Merge pull request #6618 from thundernest/message-view-redesign
Add the redesigned message view screen
This commit is contained in:
commit
e826ed8db7
127 changed files with 3647 additions and 1137 deletions
|
@ -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")
|
||||
|
|
|
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -57,10 +51,7 @@ public class 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;
|
||||
|
@ -75,10 +66,7 @@ public class FontSizes {
|
|||
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());
|
||||
|
@ -93,10 +81,7 @@ public class FontSizes {
|
|||
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);
|
||||
|
||||
|
@ -149,36 +134,12 @@ public class FontSizes {
|
|||
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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.fsck.k9.helper;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
|
@ -9,13 +11,12 @@ import android.content.pm.PackageManager;
|
|||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract;
|
||||
import timber.log.Timber;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Photo;
|
||||
|
||||
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.
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
@ -169,6 +173,24 @@ public class Contacts {
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package com.fsck.k9.message
|
||||
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.helper.ReplyToParser
|
||||
import com.fsck.k9.mail.Message
|
||||
|
||||
/**
|
||||
* Figures out which reply actions are available to the user.
|
||||
*/
|
||||
class ReplyActionStrategy(private val replyRoParser: ReplyToParser) {
|
||||
fun getReplyActions(account: Account, message: Message): ReplyActions {
|
||||
val recipientsToReplyTo = replyRoParser.getRecipientsToReplyTo(message, account)
|
||||
val recipientsToReplyAllTo = replyRoParser.getRecipientsToReplyAllTo(message, account)
|
||||
|
||||
val replyToAllCount = recipientsToReplyAllTo.to.size + recipientsToReplyAllTo.cc.size
|
||||
return if (replyToAllCount <= 1) {
|
||||
if (recipientsToReplyTo.to.isEmpty()) {
|
||||
ReplyActions(defaultAction = null)
|
||||
} else {
|
||||
ReplyActions(defaultAction = ReplyAction.REPLY)
|
||||
}
|
||||
} else {
|
||||
ReplyActions(defaultAction = ReplyAction.REPLY_ALL, additionalActions = listOf(ReplyAction.REPLY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ReplyActions(
|
||||
val defaultAction: ReplyAction?,
|
||||
val additionalActions: List<ReplyAction> = emptyList()
|
||||
)
|
||||
|
||||
enum class ReplyAction {
|
||||
REPLY,
|
||||
REPLY_ALL
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
package com.fsck.k9.helper
|
||||
|
||||
import android.graphics.Color
|
||||
import android.text.Spannable
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import androidx.core.text.getSpans
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Identity
|
||||
import com.fsck.k9.RobolectricTest
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
private const val IDENTITY_ADDRESS = "me@domain.example"
|
||||
private const val ME_TEXT = "me"
|
||||
|
||||
class RealAddressFormatterTest : RobolectricTest() {
|
||||
private val contactNameProvider = object : ContactNameProvider {
|
||||
override fun getNameForAddress(address: String): String? {
|
||||
return when (address) {
|
||||
"user1@domain.example" -> "Contact One"
|
||||
"spoof@domain.example" -> "contact@important.example"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val account = Account("uuid").apply {
|
||||
identities += Identity(email = IDENTITY_ADDRESS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single identity`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address(IDENTITY_ADDRESS, "irrelevant"))
|
||||
|
||||
assertThat(displayName).isEqualTo(ME_TEXT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple identities`() {
|
||||
val account = Account("uuid").apply {
|
||||
identities += Identity(description = "My identity", email = IDENTITY_ADDRESS)
|
||||
identities += Identity(email = "another.one@domain.example")
|
||||
}
|
||||
val addressFormatter = createAddressFormatter(account)
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address(IDENTITY_ADDRESS, "irrelevant"))
|
||||
|
||||
assertThat(displayName).isEqualTo("My identity")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `identity without a description`() {
|
||||
val account = Account("uuid").apply {
|
||||
identities += Identity(name = "My name", email = IDENTITY_ADDRESS)
|
||||
identities += Identity(email = "another.one@domain.example")
|
||||
}
|
||||
val addressFormatter = createAddressFormatter(account)
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address(IDENTITY_ADDRESS, "irrelevant"))
|
||||
|
||||
assertThat(displayName).isEqualTo("My name")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `identity without a description and name`() {
|
||||
val account = Account("uuid").apply {
|
||||
identities += Identity(email = IDENTITY_ADDRESS)
|
||||
identities += Identity(email = "another.one@domain.example")
|
||||
}
|
||||
val addressFormatter = createAddressFormatter(account)
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address(IDENTITY_ADDRESS, "irrelevant"))
|
||||
|
||||
assertThat(displayName).isEqualTo(IDENTITY_ADDRESS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email address without display name`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("alice@domain.example"))
|
||||
|
||||
assertThat(displayName).isEqualTo("alice@domain.example")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email address with display name`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("alice@domain.example", "Alice"))
|
||||
|
||||
assertThat(displayName).isEqualTo("Alice")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `don't look up contact when showContactNames = false`() {
|
||||
val addressFormatter = createAddressFormatter(showContactNames = false)
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("user1@domain.example", "User 1"))
|
||||
|
||||
assertThat(displayName).isEqualTo("User 1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contact lookup`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("user1@domain.example"))
|
||||
|
||||
assertThat(displayName).isEqualTo("Contact One")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contact lookup despite display name`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("user1@domain.example", "User 1"))
|
||||
|
||||
assertThat(displayName).isEqualTo("Contact One")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `colored contact name`() {
|
||||
val addressFormatter = createAddressFormatter(contactNameColor = Color.RED)
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("user1@domain.example"))
|
||||
|
||||
assertThat(displayName.toString()).isEqualTo("Contact One")
|
||||
assertThat(displayName).isInstanceOf(Spannable::class.java)
|
||||
val spans = (displayName as Spannable).getSpans<ForegroundColorSpan>(0, displayName.length)
|
||||
assertThat(spans.map { it.foregroundColor }).containsExactly(Color.RED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email address with display name but not showing correspondent names`() {
|
||||
val addressFormatter = createAddressFormatter(showCorrespondentNames = false)
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("alice@domain.example", "Alice"))
|
||||
|
||||
assertThat(displayName).isEqualTo("alice@domain.example")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `do not show display name that looks like an email address`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("mallory@domain.example", "potus@whitehouse.gov"))
|
||||
|
||||
assertThat(displayName).isEqualTo("mallory@domain.example")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `do show display name that contains an @ preceded by an opening parenthesis`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("gitlab@gitlab.example", "username (@username)"))
|
||||
|
||||
assertThat(displayName).isEqualTo("username (@username)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `do show display name that starts with an @`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("address@domain.example", "@username"))
|
||||
|
||||
assertThat(displayName).isEqualTo("@username")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `spoof prevention doesn't apply to contact names`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("spoof@domain.example", "contact@important.example"))
|
||||
|
||||
assertThat(displayName).isEqualTo("contact@important.example")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `display name matches me text`() {
|
||||
val addressFormatter = createAddressFormatter()
|
||||
|
||||
val displayName = addressFormatter.getDisplayName(Address("someone_named_me@domain.example", "ME"))
|
||||
|
||||
assertThat(displayName).isEqualTo("someone_named_me@domain.example")
|
||||
}
|
||||
|
||||
private fun createAddressFormatter(
|
||||
account: Account = this.account,
|
||||
showCorrespondentNames: Boolean = true,
|
||||
showContactNames: Boolean = true,
|
||||
contactNameColor: Int? = null
|
||||
): RealAddressFormatter {
|
||||
return RealAddressFormatter(
|
||||
contactNameProvider = contactNameProvider,
|
||||
account = account,
|
||||
showCorrespondentNames = showCorrespondentNames,
|
||||
showContactNames = showContactNames,
|
||||
contactNameColor = contactNameColor,
|
||||
meText = ME_TEXT
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package com.fsck.k9.message
|
||||
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Identity
|
||||
import com.fsck.k9.helper.ReplyToParser
|
||||
import com.fsck.k9.mail.buildMessage
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
private const val IDENTITY_EMAIL_ADDRESS = "myself@domain.example"
|
||||
|
||||
class ReplyActionStrategyTest {
|
||||
private val account = createAccount()
|
||||
private val replyActionStrategy = ReplyActionStrategy(ReplyToParser())
|
||||
|
||||
@Test
|
||||
fun `message sent to only our identity`() {
|
||||
val message = buildMessage {
|
||||
header("From", "sender@domain.example")
|
||||
header("To", IDENTITY_EMAIL_ADDRESS)
|
||||
}
|
||||
|
||||
val replyActions = replyActionStrategy.getReplyActions(account, message)
|
||||
|
||||
assertThat(replyActions.defaultAction).isEqualTo(ReplyAction.REPLY)
|
||||
assertThat(replyActions.additionalActions).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message sent to our identity and others`() {
|
||||
val message = buildMessage {
|
||||
header("From", "sender@domain.example")
|
||||
header("To", "$IDENTITY_EMAIL_ADDRESS, other@domain.example")
|
||||
}
|
||||
|
||||
val replyActions = replyActionStrategy.getReplyActions(account, message)
|
||||
|
||||
assertThat(replyActions.defaultAction).isEqualTo(ReplyAction.REPLY_ALL)
|
||||
assertThat(replyActions.additionalActions).containsExactly(ReplyAction.REPLY)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message sent to our identity and others (CC)`() {
|
||||
val message = buildMessage {
|
||||
header("From", "sender@domain.example")
|
||||
header("Cc", "$IDENTITY_EMAIL_ADDRESS, other@domain.example")
|
||||
}
|
||||
|
||||
val replyActions = replyActionStrategy.getReplyActions(account, message)
|
||||
|
||||
assertThat(replyActions.defaultAction).isEqualTo(ReplyAction.REPLY_ALL)
|
||||
assertThat(replyActions.additionalActions).containsExactly(ReplyAction.REPLY)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message sent to our identity and others (To+CC)`() {
|
||||
val message = buildMessage {
|
||||
header("From", "sender@domain.example")
|
||||
header("To", IDENTITY_EMAIL_ADDRESS)
|
||||
header("Cc", "other@domain.example")
|
||||
}
|
||||
|
||||
val replyActions = replyActionStrategy.getReplyActions(account, message)
|
||||
|
||||
assertThat(replyActions.defaultAction).isEqualTo(ReplyAction.REPLY_ALL)
|
||||
assertThat(replyActions.additionalActions).containsExactly(ReplyAction.REPLY)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message sent to our identity and others (CC+To)`() {
|
||||
val message = buildMessage {
|
||||
header("From", "sender@domain.example")
|
||||
header("To", "other@domain.example")
|
||||
header("Cc", IDENTITY_EMAIL_ADDRESS)
|
||||
}
|
||||
|
||||
val replyActions = replyActionStrategy.getReplyActions(account, message)
|
||||
|
||||
assertThat(replyActions.defaultAction).isEqualTo(ReplyAction.REPLY_ALL)
|
||||
assertThat(replyActions.additionalActions).containsExactly(ReplyAction.REPLY)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message where neither sender nor recipient addresses belong to account`() {
|
||||
val message = buildMessage {
|
||||
header("From", "sender@domain.example")
|
||||
header("To", "recipient@domain.example")
|
||||
}
|
||||
|
||||
val replyActions = replyActionStrategy.getReplyActions(account, message)
|
||||
|
||||
assertThat(replyActions.defaultAction).isEqualTo(ReplyAction.REPLY_ALL)
|
||||
assertThat(replyActions.additionalActions).containsExactly(ReplyAction.REPLY)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message without any sender or recipient headers`() {
|
||||
val message = buildMessage {}
|
||||
|
||||
val replyActions = replyActionStrategy.getReplyActions(account, message)
|
||||
|
||||
assertThat(replyActions.defaultAction).isNull()
|
||||
assertThat(replyActions.additionalActions).isEmpty()
|
||||
}
|
||||
|
||||
private fun createAccount(): Account {
|
||||
return Account("00000000-0000-4000-0000-000000000000").apply {
|
||||
identities += Identity(name = "Myself", email = IDENTITY_EMAIL_ADDRESS)
|
||||
}
|
||||
}
|
||||
}
|
4
app/k9mail/proguard-rules.pro
vendored
4
app/k9mail/proguard-rules.pro
vendored
|
@ -29,6 +29,10 @@
|
|||
-dontnote com.fsck.k9.ui.messageview.**
|
||||
-dontnote com.fsck.k9.view.**
|
||||
|
||||
-assumevalues class * extends android.view.View {
|
||||
boolean isInEditMode() return false;
|
||||
}
|
||||
|
||||
-keep public class org.openintents.openpgp.**
|
||||
|
||||
-keepclassmembers class * extends androidx.appcompat.widget.SearchView {
|
||||
|
|
|
@ -133,6 +133,10 @@ class K9MessageStore(
|
|||
return retrieveMessageOperations.getHeaders(folderId, messageServerId)
|
||||
}
|
||||
|
||||
override fun getHeaders(folderId: Long, messageServerId: String, headerNames: Set<String>): List<Header> {
|
||||
return retrieveMessageOperations.getHeaders(folderId, messageServerId, headerNames)
|
||||
}
|
||||
|
||||
override fun destroyMessages(folderId: Long, messageServerIds: Collection<String>) {
|
||||
deleteMessageOperations.destroyMessages(folderId, messageServerIds)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.fsck.k9.storage.messages
|
|||
|
||||
import androidx.core.database.getLongOrNull
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.helper.mapToSet
|
||||
import com.fsck.k9.mail.Flag
|
||||
import com.fsck.k9.mail.Header
|
||||
import com.fsck.k9.mail.internet.MimeHeader
|
||||
|
@ -167,7 +168,7 @@ internal class RetrieveMessageOperations(private val lockableDatabase: LockableD
|
|||
}
|
||||
}
|
||||
|
||||
fun getHeaders(folderId: Long, messageServerId: String): List<Header> {
|
||||
fun getHeaders(folderId: Long, messageServerId: String, headerNames: Set<String>? = null): List<Header> {
|
||||
return lockableDatabase.execute(false) { database ->
|
||||
database.rawQuery(
|
||||
"SELECT message_parts.header FROM messages" +
|
||||
|
@ -178,10 +179,13 @@ internal class RetrieveMessageOperations(private val lockableDatabase: LockableD
|
|||
if (!cursor.moveToFirst()) throw MessageNotFoundException(folderId, messageServerId)
|
||||
|
||||
val headerBytes = cursor.getBlob(0)
|
||||
val lowercaseHeaderNames = headerNames?.mapToSet(headerNames.size) { it.lowercase() }
|
||||
|
||||
val header = MimeHeader()
|
||||
MessageHeaderParser.parse(headerBytes.inputStream()) { name, value ->
|
||||
header.addRawHeader(name, value)
|
||||
if (lowercaseHeaderNames == null || name.lowercase() in lowercaseHeaderNames) {
|
||||
header.addRawHeader(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
header.headers
|
||||
|
|
|
@ -170,6 +170,34 @@ class RetrieveMessageOperationsTest : RobolectricTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get some headers`() {
|
||||
val messagePartId = sqliteDatabase.createMessagePart(
|
||||
header = """
|
||||
From: <alice@domain.example>
|
||||
To: Bob <bob@domain.example>
|
||||
Date: Thu, 01 Apr 2021 01:23:45 +0200
|
||||
Subject: Test
|
||||
Message-Id: <20210401012345.123456789A@domain.example>
|
||||
""".trimIndent().crlf()
|
||||
)
|
||||
sqliteDatabase.createMessage(folderId = 1, uid = "uid1", messagePartId = messagePartId)
|
||||
|
||||
val headers = retrieveMessageOperations.getHeaders(
|
||||
folderId = 1,
|
||||
messageServerId = "uid1",
|
||||
headerNames = setOf("from", "to", "message-id")
|
||||
)
|
||||
|
||||
assertThat(headers).isEqualTo(
|
||||
listOf(
|
||||
Header("From", "<alice@domain.example>"),
|
||||
Header("To", "Bob <bob@domain.example>"),
|
||||
Header("Message-Id", "<20210401012345.123456789A@domain.example>")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get oldest message date`() {
|
||||
sqliteDatabase.createMessage(folderId = 1, date = 42)
|
||||
|
|
|
@ -11,6 +11,7 @@ dependencies {
|
|||
implementation project(":app:autodiscovery:api")
|
||||
implementation project(":app:autodiscovery:providersxml")
|
||||
implementation project(":mail:common")
|
||||
implementation project(":ui-utils:ToolbarBottomSheet")
|
||||
|
||||
//TODO: Remove AccountSetupIncoming's dependency on these
|
||||
compileOnly project(":mail:protocols:imap")
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.fsck.k9.ui.choosefolder.chooseFolderUiModule
|
|||
import com.fsck.k9.ui.endtoend.endToEndUiModule
|
||||
import com.fsck.k9.ui.folders.foldersUiModule
|
||||
import com.fsck.k9.ui.managefolders.manageFoldersUiModule
|
||||
import com.fsck.k9.ui.messagedetails.messageDetailsUiModule
|
||||
import com.fsck.k9.ui.messagelist.messageListUiModule
|
||||
import com.fsck.k9.ui.messagesource.messageSourceModule
|
||||
import com.fsck.k9.ui.settings.settingsUiModule
|
||||
|
@ -33,5 +34,6 @@ val uiModules = listOf(
|
|||
viewModule,
|
||||
changelogUiModule,
|
||||
messageSourceModule,
|
||||
accountUiModule
|
||||
accountUiModule,
|
||||
messageDetailsUiModule
|
||||
)
|
||||
|
|
|
@ -129,6 +129,9 @@ open class MessageList :
|
|||
private var messageListWasDisplayed = false
|
||||
private var viewSwitcher: ViewSwitcher? = null
|
||||
|
||||
private val isShowAccountChip: Boolean
|
||||
get() = messageListFragment?.isShowAccountChip ?: true
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -956,7 +959,7 @@ open class MessageList :
|
|||
displayMode = DisplayMode.MESSAGE_LIST
|
||||
MessageActions.actionEditDraft(this, messageReference)
|
||||
} else {
|
||||
val fragment = MessageViewContainerFragment.newInstance(messageReference)
|
||||
val fragment = MessageViewContainerFragment.newInstance(messageReference, isShowAccountChip)
|
||||
supportFragmentManager.commitNow {
|
||||
replace(R.id.message_view_container, fragment, FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER)
|
||||
}
|
||||
|
|
|
@ -27,10 +27,7 @@ data class MessageListActivityConfig(
|
|||
val fontSizeMessageListDate: Int,
|
||||
val fontSizeMessageListPreview: Int,
|
||||
val fontSizeMessageViewSender: Int,
|
||||
val fontSizeMessageViewTo: Int,
|
||||
val fontSizeMessageViewCC: Int,
|
||||
val fontSizeMessageViewBCC: Int,
|
||||
val fontSizeMessageViewAdditionalHeaders: Int,
|
||||
val fontSizeMessageViewRecipients: Int,
|
||||
val fontSizeMessageViewSubject: Int,
|
||||
val fontSizeMessageViewDate: Int,
|
||||
val fontSizeMessageViewContentAsPercent: Int,
|
||||
|
@ -62,10 +59,7 @@ data class MessageListActivityConfig(
|
|||
fontSizeMessageListDate = K9.fontSizes.messageListDate,
|
||||
fontSizeMessageListPreview = K9.fontSizes.messageListPreview,
|
||||
fontSizeMessageViewSender = K9.fontSizes.messageViewSender,
|
||||
fontSizeMessageViewTo = K9.fontSizes.messageViewTo,
|
||||
fontSizeMessageViewCC = K9.fontSizes.messageViewCC,
|
||||
fontSizeMessageViewBCC = K9.fontSizes.messageViewBCC,
|
||||
fontSizeMessageViewAdditionalHeaders = K9.fontSizes.messageViewAdditionalHeaders,
|
||||
fontSizeMessageViewRecipients = K9.fontSizes.messageViewRecipients,
|
||||
fontSizeMessageViewSubject = K9.fontSizes.messageViewSubject,
|
||||
fontSizeMessageViewDate = K9.fontSizes.messageViewDate,
|
||||
fontSizeMessageViewContentAsPercent = K9.fontSizes.messageViewContentAsPercent,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package com.fsck.k9.ui.helper
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
|
||||
/**
|
||||
* Return the baseline of the last line of text, instead of TextView's default of the first line.
|
||||
*/
|
||||
// Source: https://stackoverflow.com/a/62419876
|
||||
class BottomBaselineTextView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : AppCompatTextView(context, attrs) {
|
||||
|
||||
override fun getBaseline(): Int {
|
||||
val layout = layout ?: return super.getBaseline()
|
||||
val baselineOffset = super.getBaseline() - layout.getLineBaseline(0)
|
||||
return baselineOffset + layout.getLineBaseline(layout.lineCount - 1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.ContactsContract
|
||||
|
||||
internal class AddToContactsLauncher {
|
||||
fun launch(context: Context, name: String?, email: String) {
|
||||
val intent = Intent(Intent.ACTION_INSERT).apply {
|
||||
type = ContactsContract.Contacts.CONTENT_TYPE
|
||||
|
||||
putExtra(ContactsContract.Intents.Insert.EMAIL, email)
|
||||
|
||||
if (name != null) {
|
||||
putExtra(ContactsContract.Intents.Insert.NAME, name)
|
||||
}
|
||||
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import com.fsck.k9.K9
|
||||
|
||||
class ContactSettingsProvider {
|
||||
val isShowContactPicture: Boolean
|
||||
get() = K9.isShowContactPicture
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.ui.resolveColorAttribute
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.mikepenz.fastadapter.items.AbstractItem
|
||||
|
||||
internal class CryptoStatusItem(val cryptoDetails: CryptoDetails) : AbstractItem<CryptoStatusItem.ViewHolder>() {
|
||||
override val type = R.id.message_details_crypto_status
|
||||
override val layoutRes = R.layout.message_details_crypto_status_item
|
||||
|
||||
override fun getViewHolder(v: View) = ViewHolder(v)
|
||||
|
||||
class ViewHolder(view: View) : FastAdapter.ViewHolder<CryptoStatusItem>(view) {
|
||||
private val titleTextView = view.findViewById<TextView>(R.id.crypto_status_title)
|
||||
private val descriptionTextView = view.findViewById<TextView>(R.id.crypto_status_description)
|
||||
private val imageView = view.findViewById<ImageView>(R.id.crypto_status_icon)
|
||||
private val originalBackground = view.background
|
||||
|
||||
override fun bindView(item: CryptoStatusItem, payloads: List<Any>) {
|
||||
val context = itemView.context
|
||||
val cryptoDetails = item.cryptoDetails
|
||||
val cryptoStatus = cryptoDetails.cryptoStatus
|
||||
|
||||
imageView.setImageResource(cryptoStatus.statusIconRes)
|
||||
val tintColor = context.theme.resolveColorAttribute(cryptoStatus.colorAttr)
|
||||
imageView.imageTintList = ColorStateList.valueOf(tintColor)
|
||||
|
||||
cryptoStatus.titleTextRes?.let { stringResId ->
|
||||
titleTextView.text = context.getString(stringResId)
|
||||
}
|
||||
cryptoStatus.descriptionTextRes?.let { stringResId ->
|
||||
descriptionTextView.text = context.getString(stringResId)
|
||||
}
|
||||
|
||||
if (!cryptoDetails.isClickable) {
|
||||
itemView.background = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun unbindView(item: CryptoStatusItem) {
|
||||
imageView.setImageDrawable(null)
|
||||
titleTextView.text = null
|
||||
descriptionTextView.text = null
|
||||
itemView.background = originalBackground
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val messageDetailsUiModule = module {
|
||||
viewModel {
|
||||
MessageDetailsViewModel(
|
||||
resources = get(),
|
||||
messageRepository = get(),
|
||||
contactSettingsProvider = get(),
|
||||
contacts = get(),
|
||||
clipboardManager = get()
|
||||
)
|
||||
}
|
||||
factory { ContactSettingsProvider() }
|
||||
factory { AddToContactsLauncher() }
|
||||
factory { ShowContactLauncher() }
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.fsck.k9.ui.R
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.mikepenz.fastadapter.items.AbstractItem
|
||||
|
||||
internal class MessageDateItem(private val date: String) : AbstractItem<MessageDateItem.ViewHolder>() {
|
||||
override val type: Int = R.id.message_details_date
|
||||
override val layoutRes = R.layout.message_details_date_item
|
||||
|
||||
override fun getViewHolder(v: View) = ViewHolder(v)
|
||||
|
||||
class ViewHolder(view: View) : FastAdapter.ViewHolder<MessageDateItem>(view) {
|
||||
private val textView = view.findViewById<TextView>(R.id.date)
|
||||
|
||||
override fun bindView(item: MessageDateItem, payloads: List<Any>) {
|
||||
textView.text = item.date
|
||||
}
|
||||
|
||||
override fun unbindView(item: MessageDateItem) {
|
||||
textView.text = null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.view.View
|
||||
import com.fsck.k9.ui.R
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.mikepenz.fastadapter.items.AbstractItem
|
||||
|
||||
internal class MessageDetailsDividerItem : AbstractItem<MessageDetailsDividerItem.ViewHolder>() {
|
||||
override val type: Int = R.id.message_details_divider
|
||||
override val layoutRes = R.layout.message_details_divider_item
|
||||
|
||||
override fun getViewHolder(v: View) = ViewHolder(v)
|
||||
|
||||
class ViewHolder(view: View) : FastAdapter.ViewHolder<MessageDetailsDividerItem>(view) {
|
||||
override fun bindView(item: MessageDetailsDividerItem, payloads: List<Any>) = Unit
|
||||
|
||||
override fun unbindView(item: MessageDetailsDividerItem) = Unit
|
||||
}
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ProgressBar
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import app.k9mail.ui.utils.bottomsheet.ToolbarBottomSheetDialogFragment
|
||||
import com.fsck.k9.activity.MessageCompose
|
||||
import com.fsck.k9.contacts.ContactPictureLoader
|
||||
import com.fsck.k9.controller.MessageReference
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.mailstore.CryptoResultAnnotation
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.ui.observe
|
||||
import com.fsck.k9.ui.withArguments
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.mikepenz.fastadapter.GenericItem
|
||||
import com.mikepenz.fastadapter.adapters.ItemAdapter
|
||||
import com.mikepenz.fastadapter.listeners.ClickEventHook
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class MessageDetailsFragment : ToolbarBottomSheetDialogFragment() {
|
||||
private val viewModel: MessageDetailsViewModel by viewModel()
|
||||
private val addToContactsLauncher: AddToContactsLauncher by inject()
|
||||
private val showContactLauncher: ShowContactLauncher by inject()
|
||||
private val contactPictureLoader: ContactPictureLoader by inject()
|
||||
|
||||
private lateinit var messageReference: MessageReference
|
||||
|
||||
// FIXME: Replace this with a mechanism that survives process death
|
||||
var cryptoResult: CryptoResultAnnotation? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
messageReference = MessageReference.parse(arguments?.getString(ARG_REFERENCE))
|
||||
?: error("Missing argument $ARG_REFERENCE")
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.message_bottom_sheet, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
cryptoResult?.let {
|
||||
viewModel.cryptoResult = it
|
||||
}
|
||||
|
||||
val dialog = checkNotNull(dialog)
|
||||
dialog.isDismissWithAnimation = true
|
||||
|
||||
val toolbar = checkNotNull(toolbar)
|
||||
toolbar.apply {
|
||||
title = getString(R.string.message_details_toolbar_title)
|
||||
navigationIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_close)
|
||||
|
||||
setNavigationOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
val progressBar = view.findViewById<ProgressBar>(R.id.message_details_progress)
|
||||
val errorView = view.findViewById<View>(R.id.message_details_error)
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.message_details_list)
|
||||
|
||||
viewModel.uiEvents.observe(this) { event ->
|
||||
when (event) {
|
||||
is MessageDetailEvent.ShowCryptoKeys -> showCryptoKeys(event.pendingIntent)
|
||||
MessageDetailEvent.SearchCryptoKeys -> searchCryptoKeys()
|
||||
MessageDetailEvent.ShowCryptoWarning -> showCryptoWarning()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.loadData(messageReference).observe(this) { state ->
|
||||
when (state) {
|
||||
MessageDetailsState.Loading -> {
|
||||
progressBar.isVisible = true
|
||||
errorView.isVisible = false
|
||||
recyclerView.isVisible = false
|
||||
}
|
||||
MessageDetailsState.Error -> {
|
||||
progressBar.isVisible = false
|
||||
errorView.isVisible = true
|
||||
recyclerView.isVisible = false
|
||||
}
|
||||
is MessageDetailsState.DataLoaded -> {
|
||||
progressBar.isVisible = false
|
||||
errorView.isVisible = false
|
||||
recyclerView.isVisible = true
|
||||
setMessageDetails(recyclerView, state.details, state.showContactPicture)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMessageDetails(recyclerView: RecyclerView, details: MessageDetailsUi, showContactPicture: Boolean) {
|
||||
val itemAdapter = ItemAdapter<GenericItem>().apply {
|
||||
add(MessageDateItem(details.date ?: getString(R.string.message_details_missing_date)))
|
||||
|
||||
if (details.cryptoDetails != null) {
|
||||
add(CryptoStatusItem(details.cryptoDetails))
|
||||
}
|
||||
|
||||
addParticipants(details.from, R.string.message_details_from_section_title, showContactPicture)
|
||||
addParticipants(details.sender, R.string.message_details_sender_section_title, showContactPicture)
|
||||
addParticipants(details.replyTo, R.string.message_details_replyto_section_title, showContactPicture)
|
||||
|
||||
add(MessageDetailsDividerItem())
|
||||
|
||||
addParticipants(details.to, R.string.message_details_to_section_title, showContactPicture)
|
||||
addParticipants(details.cc, R.string.message_details_cc_section_title, showContactPicture)
|
||||
addParticipants(details.bcc, R.string.message_details_bcc_section_title, showContactPicture)
|
||||
}
|
||||
|
||||
val adapter = FastAdapter.with(itemAdapter).apply {
|
||||
addEventHook(cryptoStatusClickEventHook)
|
||||
addEventHook(participantClickEventHook)
|
||||
addEventHook(addToContactsClickEventHook)
|
||||
addEventHook(composeClickEventHook)
|
||||
addEventHook(overflowClickEventHook)
|
||||
}
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
private fun ItemAdapter<GenericItem>.addParticipants(
|
||||
participants: List<Participant>,
|
||||
@StringRes title: Int,
|
||||
showContactPicture: Boolean
|
||||
) {
|
||||
if (participants.isNotEmpty()) {
|
||||
val extraText = if (participants.size > 1) participants.size.toString() else null
|
||||
add(SectionHeaderItem(title = getString(title), extra = extraText))
|
||||
|
||||
for (participant in participants) {
|
||||
add(ParticipantItem(contactPictureLoader, showContactPicture, participant))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val cryptoStatusClickEventHook = object : ClickEventHook<CryptoStatusItem>() {
|
||||
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? {
|
||||
return if (viewHolder is CryptoStatusItem.ViewHolder) {
|
||||
viewHolder.itemView
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(
|
||||
v: View,
|
||||
position: Int,
|
||||
fastAdapter: FastAdapter<CryptoStatusItem>,
|
||||
item: CryptoStatusItem
|
||||
) {
|
||||
if (item.cryptoDetails.isClickable) {
|
||||
viewModel.onCryptoStatusClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val participantClickEventHook = object : ClickEventHook<ParticipantItem>() {
|
||||
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? {
|
||||
return if (viewHolder is ParticipantItem.ViewHolder) {
|
||||
viewHolder.itemView
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View, position: Int, fastAdapter: FastAdapter<ParticipantItem>, item: ParticipantItem) {
|
||||
val contactLookupUri = item.participant.contactLookupUri ?: return
|
||||
showContact(contactLookupUri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showContact(contactLookupUri: Uri) {
|
||||
showContactLauncher.launch(requireContext(), contactLookupUri)
|
||||
}
|
||||
|
||||
private val addToContactsClickEventHook = object : ClickEventHook<ParticipantItem>() {
|
||||
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? {
|
||||
return if (viewHolder is ParticipantItem.ViewHolder) {
|
||||
viewHolder.menuAddContact
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View, position: Int, fastAdapter: FastAdapter<ParticipantItem>, item: ParticipantItem) {
|
||||
val address = item.participant.address
|
||||
addToContacts(address)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToContacts(address: Address) {
|
||||
addToContactsLauncher.launch(context = requireContext(), name = address.personal, email = address.address)
|
||||
}
|
||||
|
||||
private val composeClickEventHook = object : ClickEventHook<ParticipantItem>() {
|
||||
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? {
|
||||
return if (viewHolder is ParticipantItem.ViewHolder) {
|
||||
viewHolder.menuCompose
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View, position: Int, fastAdapter: FastAdapter<ParticipantItem>, item: ParticipantItem) {
|
||||
val address = item.participant.address
|
||||
composeMessageToAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
private fun composeMessageToAddress(address: Address) {
|
||||
// TODO: Use the identity this message was sent to as sender identity
|
||||
|
||||
val intent = Intent(context, MessageCompose::class.java).apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(address.toString()))
|
||||
putExtra(MessageCompose.EXTRA_ACCOUNT, messageReference.accountUuid)
|
||||
}
|
||||
|
||||
dismiss()
|
||||
requireContext().startActivity(intent)
|
||||
}
|
||||
|
||||
private val overflowClickEventHook = object : ClickEventHook<ParticipantItem>() {
|
||||
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? {
|
||||
return if (viewHolder is ParticipantItem.ViewHolder) {
|
||||
viewHolder.menuOverflow
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View, position: Int, fastAdapter: FastAdapter<ParticipantItem>, item: ParticipantItem) {
|
||||
showOverflowMenu(v, item.participant)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOverflowMenu(view: View, participant: Participant) {
|
||||
val popupMenu = PopupMenu(requireContext(), view).apply {
|
||||
inflate(R.menu.participant_overflow_menu)
|
||||
}
|
||||
|
||||
if (participant.address.personal == null) {
|
||||
popupMenu.menu.findItem(R.id.copy_name_and_email_address).isVisible = false
|
||||
}
|
||||
|
||||
popupMenu.setOnMenuItemClickListener { item: MenuItem ->
|
||||
onOverflowMenuItemClick(item.itemId, participant)
|
||||
true
|
||||
}
|
||||
|
||||
popupMenu.show()
|
||||
}
|
||||
|
||||
private fun onOverflowMenuItemClick(itemId: Int, participant: Participant) {
|
||||
when (itemId) {
|
||||
R.id.copy_email_address -> viewModel.onCopyEmailAddressToClipboard(participant)
|
||||
R.id.copy_name_and_email_address -> viewModel.onCopyNameAndEmailAddressToClipboard(participant)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCryptoKeys(pendingIntent: PendingIntent) {
|
||||
requireActivity().startIntentSender(pendingIntent.intentSender, null, 0, 0, 0)
|
||||
}
|
||||
|
||||
private fun searchCryptoKeys() {
|
||||
setFragmentResult(FRAGMENT_RESULT_KEY, bundleOf(RESULT_ACTION to ACTION_SEARCH_KEYS))
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun showCryptoWarning() {
|
||||
setFragmentResult(FRAGMENT_RESULT_KEY, bundleOf(RESULT_ACTION to ACTION_SHOW_WARNING))
|
||||
dismiss()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ARG_REFERENCE = "reference"
|
||||
|
||||
const val FRAGMENT_RESULT_KEY = "messageDetailsResult"
|
||||
const val RESULT_ACTION = "action"
|
||||
const val ACTION_SEARCH_KEYS = "search_keys"
|
||||
const val ACTION_SHOW_WARNING = "show_warning"
|
||||
|
||||
fun create(messageReference: MessageReference): MessageDetailsFragment {
|
||||
return MessageDetailsFragment().withArguments(
|
||||
ARG_REFERENCE to messageReference.toIdentityString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.net.Uri
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.view.MessageCryptoDisplayStatus
|
||||
|
||||
data class MessageDetailsUi(
|
||||
val date: String?,
|
||||
val cryptoDetails: CryptoDetails?,
|
||||
val from: List<Participant>,
|
||||
val sender: List<Participant>,
|
||||
val replyTo: List<Participant>,
|
||||
val to: List<Participant>,
|
||||
val cc: List<Participant>,
|
||||
val bcc: List<Participant>
|
||||
)
|
||||
|
||||
data class CryptoDetails(
|
||||
val cryptoStatus: MessageCryptoDisplayStatus,
|
||||
val isClickable: Boolean
|
||||
)
|
||||
|
||||
data class Participant(
|
||||
val address: Address,
|
||||
val contactLookupUri: Uri?
|
||||
) {
|
||||
val isInContacts: Boolean
|
||||
get() = contactLookupUri != null
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.res.Resources
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.fsck.k9.controller.MessageReference
|
||||
import com.fsck.k9.helper.ClipboardManager
|
||||
import com.fsck.k9.helper.Contacts
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.mailstore.CryptoResultAnnotation
|
||||
import com.fsck.k9.mailstore.MessageDate
|
||||
import com.fsck.k9.mailstore.MessageRepository
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.view.MessageCryptoDisplayStatus
|
||||
import java.text.DateFormat
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class MessageDetailsViewModel(
|
||||
private val resources: Resources,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val contactSettingsProvider: ContactSettingsProvider,
|
||||
private val contacts: Contacts,
|
||||
private val clipboardManager: ClipboardManager
|
||||
) : ViewModel() {
|
||||
private val dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.getDefault())
|
||||
private val uiState = MutableStateFlow<MessageDetailsState>(MessageDetailsState.Loading)
|
||||
private val eventChannel = Channel<MessageDetailEvent>()
|
||||
|
||||
val uiEvents = eventChannel.receiveAsFlow()
|
||||
var cryptoResult: CryptoResultAnnotation? = null
|
||||
|
||||
fun loadData(messageReference: MessageReference): StateFlow<MessageDetailsState> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
uiState.value = try {
|
||||
val messageDetails = messageRepository.getMessageDetails(messageReference)
|
||||
|
||||
val senderList = messageDetails.sender?.let { listOf(it) } ?: emptyList()
|
||||
val messageDetailsUi = MessageDetailsUi(
|
||||
date = buildDisplayDate(messageDetails.date),
|
||||
cryptoDetails = cryptoResult?.toCryptoDetails(),
|
||||
from = messageDetails.from.toParticipants(),
|
||||
sender = senderList.toParticipants(),
|
||||
replyTo = messageDetails.replyTo.toParticipants(),
|
||||
to = messageDetails.to.toParticipants(),
|
||||
cc = messageDetails.cc.toParticipants(),
|
||||
bcc = messageDetails.bcc.toParticipants()
|
||||
)
|
||||
|
||||
MessageDetailsState.DataLoaded(
|
||||
showContactPicture = contactSettingsProvider.isShowContactPicture,
|
||||
details = messageDetailsUi
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
MessageDetailsState.Error
|
||||
}
|
||||
}
|
||||
|
||||
return uiState
|
||||
}
|
||||
|
||||
private fun buildDisplayDate(messageDate: MessageDate): String? {
|
||||
return when (messageDate) {
|
||||
is MessageDate.InvalidDate -> messageDate.dateHeader
|
||||
MessageDate.MissingDate -> null
|
||||
is MessageDate.ValidDate -> dateFormat.format(messageDate.date)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoResultAnnotation.toCryptoDetails(): CryptoDetails {
|
||||
val messageCryptoDisplayStatus = MessageCryptoDisplayStatus.fromResultAnnotation(this)
|
||||
return CryptoDetails(
|
||||
cryptoStatus = messageCryptoDisplayStatus,
|
||||
isClickable = messageCryptoDisplayStatus.hasAssociatedKey() || messageCryptoDisplayStatus.isUnknownKey ||
|
||||
hasOpenPgpInsecureWarningPendingIntent()
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<Address>.toParticipants(): List<Participant> {
|
||||
return this.map { address ->
|
||||
Participant(
|
||||
address = address,
|
||||
contactLookupUri = contacts.getContactUri(address.address)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCryptoStatusClicked() {
|
||||
val cryptoResult = cryptoResult ?: return
|
||||
val cryptoStatus = MessageCryptoDisplayStatus.fromResultAnnotation(cryptoResult)
|
||||
|
||||
if (cryptoStatus.hasAssociatedKey()) {
|
||||
val pendingIntent = cryptoResult.openPgpSigningKeyIntentIfAny
|
||||
if (pendingIntent != null) {
|
||||
viewModelScope.launch {
|
||||
eventChannel.send(MessageDetailEvent.ShowCryptoKeys(pendingIntent))
|
||||
}
|
||||
}
|
||||
} else if (cryptoStatus.isUnknownKey) {
|
||||
viewModelScope.launch {
|
||||
eventChannel.send(MessageDetailEvent.SearchCryptoKeys)
|
||||
}
|
||||
} else if (cryptoResult.hasOpenPgpInsecureWarningPendingIntent()) {
|
||||
viewModelScope.launch {
|
||||
eventChannel.send(MessageDetailEvent.ShowCryptoWarning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onCopyEmailAddressToClipboard(participant: Participant) {
|
||||
val label = resources.getString(R.string.clipboard_label_email_address)
|
||||
val emailAddress = participant.address.address
|
||||
clipboardManager.setText(label, emailAddress)
|
||||
}
|
||||
|
||||
fun onCopyNameAndEmailAddressToClipboard(participant: Participant) {
|
||||
val label = resources.getString(R.string.clipboard_label_name_and_email_address)
|
||||
val nameAndEmailAddress = participant.address.toString()
|
||||
clipboardManager.setText(label, nameAndEmailAddress)
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface MessageDetailsState {
|
||||
object Loading : MessageDetailsState
|
||||
object Error : MessageDetailsState
|
||||
data class DataLoaded(
|
||||
val showContactPicture: Boolean,
|
||||
val details: MessageDetailsUi
|
||||
) : MessageDetailsState
|
||||
}
|
||||
|
||||
sealed interface MessageDetailEvent {
|
||||
data class ShowCryptoKeys(val pendingIntent: PendingIntent) : MessageDetailEvent
|
||||
object SearchCryptoKeys : MessageDetailEvent
|
||||
object ShowCryptoWarning : MessageDetailEvent
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.fsck.k9.contacts.ContactPictureLoader
|
||||
import com.fsck.k9.ui.R
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.mikepenz.fastadapter.items.AbstractItem
|
||||
|
||||
internal class ParticipantItem(
|
||||
private val contactPictureLoader: ContactPictureLoader,
|
||||
private val showContactsPicture: Boolean,
|
||||
val participant: Participant
|
||||
) : AbstractItem<ParticipantItem.ViewHolder>() {
|
||||
override val type: Int = R.id.message_details_participant
|
||||
override val layoutRes = R.layout.message_details_participant_item
|
||||
|
||||
override fun getViewHolder(v: View) = ViewHolder(v)
|
||||
|
||||
class ViewHolder(view: View) : FastAdapter.ViewHolder<ParticipantItem>(view) {
|
||||
val menuAddContact: View = view.findViewById(R.id.menu_add_contact)
|
||||
val menuCompose: View = view.findViewById(R.id.menu_compose)
|
||||
val menuOverflow: View = view.findViewById(R.id.menu_overflow)
|
||||
|
||||
private val contactPicture: ImageView = view.findViewById(R.id.contact_picture)
|
||||
private val name = view.findViewById<TextView>(R.id.name)
|
||||
private val email = view.findViewById<TextView>(R.id.email)
|
||||
private val originalBackground = view.background
|
||||
|
||||
init {
|
||||
TooltipCompat.setTooltipText(menuAddContact, menuAddContact.contentDescription)
|
||||
TooltipCompat.setTooltipText(menuCompose, menuCompose.contentDescription)
|
||||
TooltipCompat.setTooltipText(menuOverflow, menuOverflow.contentDescription)
|
||||
}
|
||||
|
||||
override fun bindView(item: ParticipantItem, payloads: List<Any>) {
|
||||
val participant = item.participant
|
||||
val address = participant.address
|
||||
val participantName = address.personal
|
||||
|
||||
if (participantName != null) {
|
||||
name.text = participantName
|
||||
email.text = address.address
|
||||
} else {
|
||||
name.text = address.address
|
||||
email.isVisible = false
|
||||
}
|
||||
menuAddContact.isVisible = !participant.isInContacts
|
||||
|
||||
if (item.showContactsPicture) {
|
||||
item.contactPictureLoader.setContactPicture(contactPicture, address)
|
||||
} else {
|
||||
contactPicture.isVisible = false
|
||||
}
|
||||
|
||||
if (!item.participant.isInContacts) {
|
||||
itemView.isClickable = false
|
||||
itemView.background = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun unbindView(item: ParticipantItem) {
|
||||
name.text = null
|
||||
email.text = null
|
||||
email.isVisible = true
|
||||
contactPicture.isVisible = true
|
||||
itemView.background = originalBackground
|
||||
itemView.isClickable = true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.fsck.k9.ui.R
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.mikepenz.fastadapter.items.AbstractItem
|
||||
|
||||
internal class SectionHeaderItem(
|
||||
private val title: String,
|
||||
private val extra: String?
|
||||
) : AbstractItem<SectionHeaderItem.ViewHolder>() {
|
||||
override val type: Int = R.id.message_details_section_header
|
||||
override val layoutRes = R.layout.message_details_section_header_item
|
||||
|
||||
override fun getViewHolder(v: View) = ViewHolder(v)
|
||||
|
||||
class ViewHolder(view: View) : FastAdapter.ViewHolder<SectionHeaderItem>(view) {
|
||||
private val textView = view.findViewById<TextView>(R.id.title)
|
||||
private val extraTextView = view.findViewById<TextView>(R.id.extra)
|
||||
|
||||
override fun bindView(item: SectionHeaderItem, payloads: List<Any>) {
|
||||
textView.text = item.title
|
||||
extraTextView.text = item.extra
|
||||
}
|
||||
|
||||
override fun unbindView(item: SectionHeaderItem) {
|
||||
textView.text = null
|
||||
extraTextView.text = null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
||||
internal class ShowContactLauncher {
|
||||
fun launch(context: Context, contactLookupUri: Uri) {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
data = contactLookupUri
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
|
@ -157,6 +157,9 @@ class MessageListFragment :
|
|||
maybeHideFloatingActionButton()
|
||||
}
|
||||
|
||||
val isShowAccountChip: Boolean
|
||||
get() = !isSingleAccountMode
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
|
@ -571,7 +574,7 @@ class MessageListFragment :
|
|||
showContactPicture = K9.isShowContactPicture,
|
||||
showingThreadedList = showingThreadedList,
|
||||
backGroundAsReadIndicator = K9.isUseBackgroundAsUnreadIndicator,
|
||||
showAccountChip = !isSingleAccountMode
|
||||
showAccountChip = isShowAccountChip
|
||||
)
|
||||
|
||||
private fun getFolderInfoHolder(folderId: Long, account: Account): FolderInfoHolder {
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
package com.fsck.k9.ui.messageview;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.view.MessageCryptoDisplayStatus;
|
||||
import com.fsck.k9.view.ThemeUtils;
|
||||
|
||||
|
||||
public class CryptoInfoDialog extends DialogFragment {
|
||||
public static final String ARG_DISPLAY_STATUS = "display_status";
|
||||
public static final String ARG_HAS_SECURITY_WARNING = "has_security_warning";
|
||||
|
||||
|
||||
private ImageView statusIcon;
|
||||
private TextView titleText;
|
||||
private TextView descriptionText;
|
||||
|
||||
|
||||
public static CryptoInfoDialog newInstance(MessageCryptoDisplayStatus displayStatus, boolean hasSecurityWarning) {
|
||||
CryptoInfoDialog frag = new CryptoInfoDialog();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_DISPLAY_STATUS, displayStatus.toString());
|
||||
args.putBoolean(ARG_HAS_SECURITY_WARNING, hasSecurityWarning);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams") // inflating without root element is fine for creating a dialog
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Builder b = new AlertDialog.Builder(getActivity());
|
||||
|
||||
View dialogView = LayoutInflater.from(getActivity()).inflate(R.layout.message_crypto_info_dialog, null);
|
||||
|
||||
statusIcon = dialogView.findViewById(R.id.crypto_info_top_icon_1);
|
||||
titleText = dialogView.findViewById(R.id.crypto_info_title);
|
||||
descriptionText = dialogView.findViewById(R.id.crypto_info_text);
|
||||
|
||||
MessageCryptoDisplayStatus displayStatus =
|
||||
MessageCryptoDisplayStatus.valueOf(getArguments().getString(ARG_DISPLAY_STATUS));
|
||||
setMessageForDisplayStatus(displayStatus);
|
||||
|
||||
b.setView(dialogView);
|
||||
b.setPositiveButton(R.string.crypto_info_ok, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
boolean hasSecurityWarning = getArguments().getBoolean(ARG_HAS_SECURITY_WARNING);
|
||||
if (hasSecurityWarning) {
|
||||
b.setNeutralButton(R.string.crypto_info_view_security_warning, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Fragment frag = getTargetFragment();
|
||||
if (!(frag instanceof OnClickShowCryptoKeyListener)) {
|
||||
throw new AssertionError("Displaying activity must implement OnClickShowCryptoKeyListener!");
|
||||
}
|
||||
((OnClickShowCryptoKeyListener) frag).onClickShowSecurityWarning();
|
||||
}
|
||||
});
|
||||
} else if (displayStatus.isUnknownKey()) {
|
||||
b.setNeutralButton(R.string.crypto_info_search_key, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Fragment frag = getTargetFragment();
|
||||
if (! (frag instanceof OnClickShowCryptoKeyListener)) {
|
||||
throw new AssertionError("Displaying activity must implement OnClickShowCryptoKeyListener!");
|
||||
}
|
||||
((OnClickShowCryptoKeyListener) frag).onClickSearchKey();
|
||||
}
|
||||
});
|
||||
} else if (displayStatus.hasAssociatedKey()) {
|
||||
int buttonLabel = displayStatus.isUnencryptedSigned() ?
|
||||
R.string.crypto_info_view_signer : R.string.crypto_info_view_sender;
|
||||
b.setNeutralButton(buttonLabel, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Fragment frag = getTargetFragment();
|
||||
if (! (frag instanceof OnClickShowCryptoKeyListener)) {
|
||||
throw new AssertionError("Displaying activity must implement OnClickShowCryptoKeyListener!");
|
||||
}
|
||||
((OnClickShowCryptoKeyListener) frag).onClickShowCryptoKey();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return b.create();
|
||||
}
|
||||
|
||||
private void setMessageForDisplayStatus(MessageCryptoDisplayStatus displayStatus) {
|
||||
if (displayStatus.getTitleTextRes() == null) {
|
||||
throw new AssertionError("Crypto info dialog can only be displayed for items with text!");
|
||||
}
|
||||
|
||||
setMessageSingleLine(displayStatus.getColorAttr(), displayStatus.getTitleTextRes(),
|
||||
displayStatus.getDescriptionTextRes(), displayStatus.getStatusIconRes());
|
||||
}
|
||||
|
||||
private void setMessageSingleLine(@AttrRes int colorAttr, @StringRes int titleTextRes,
|
||||
@StringRes Integer descTextRes, @DrawableRes int statusIconRes) {
|
||||
@ColorInt int color = ThemeUtils.getStyledColor(getActivity(), colorAttr);
|
||||
|
||||
statusIcon.setImageResource(statusIconRes);
|
||||
statusIcon.setColorFilter(color);
|
||||
titleText.setText(titleTextRes);
|
||||
if (descTextRes != null) {
|
||||
descriptionText.setText(descTextRes);
|
||||
descriptionText.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
descriptionText.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnClickShowCryptoKeyListener {
|
||||
void onClickShowCryptoKey();
|
||||
void onClickShowSecurityWarning();
|
||||
void onClickSearchKey();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.fsck.k9.ui.messageview
|
||||
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.helper.AddressFormatter
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.mail.Message
|
||||
|
||||
/**
|
||||
* Extract recipient names from a message to display them in the message view.
|
||||
*
|
||||
* This class extracts up to [maxNumberOfDisplayRecipients] recipients from the message and converts them to their
|
||||
* display name using an [AddressFormatter].
|
||||
*/
|
||||
internal class DisplayRecipientsExtractor(
|
||||
private val addressFormatter: AddressFormatter,
|
||||
private val maxNumberOfDisplayRecipients: Int
|
||||
) {
|
||||
fun extractDisplayRecipients(message: Message, account: Account): DisplayRecipients {
|
||||
val toRecipients = message.getRecipients(Message.RecipientType.TO)
|
||||
val ccRecipients = message.getRecipients(Message.RecipientType.CC)
|
||||
val bccRecipients = message.getRecipients(Message.RecipientType.BCC)
|
||||
|
||||
val numberOfRecipients = toRecipients.size + ccRecipients.size + bccRecipients.size
|
||||
|
||||
val identity = sequenceOf(toRecipients, ccRecipients, bccRecipients)
|
||||
.flatMap { addressArray -> addressArray.asSequence() }
|
||||
.mapNotNull { address -> account.findIdentity(address) }
|
||||
.firstOrNull()
|
||||
|
||||
val identityEmail = identity?.email
|
||||
val maxAdditionalRecipients = if (identity != null) {
|
||||
maxNumberOfDisplayRecipients - 1
|
||||
} else {
|
||||
maxNumberOfDisplayRecipients
|
||||
}
|
||||
|
||||
val recipientNames = sequenceOf(toRecipients, ccRecipients, bccRecipients)
|
||||
.flatMap { addressArray -> addressArray.asSequence() }
|
||||
.filter { address -> address.address != identityEmail }
|
||||
.map { address -> addressFormatter.getDisplayName(address) }
|
||||
.take(maxAdditionalRecipients)
|
||||
.toList()
|
||||
|
||||
return if (identity != null) {
|
||||
val identityAddress = Address(identity.email)
|
||||
val meName = addressFormatter.getDisplayName(identityAddress)
|
||||
val recipients = listOf(meName) + recipientNames
|
||||
|
||||
DisplayRecipients(recipients, numberOfRecipients)
|
||||
} else {
|
||||
DisplayRecipients(recipientNames, numberOfRecipients)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal data class DisplayRecipients(
|
||||
val recipientNames: List<CharSequence>,
|
||||
val numberOfRecipients: Int
|
||||
)
|
|
@ -20,7 +20,7 @@ import timber.log.Timber;
|
|||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class MessageCryptoPresenter implements OnCryptoClickListener {
|
||||
public class MessageCryptoPresenter {
|
||||
public static final int REQUEST_CODE_UNKNOWN_KEY = 123;
|
||||
public static final int REQUEST_CODE_SECURITY_WARNING = 124;
|
||||
|
||||
|
@ -38,6 +38,10 @@ public class MessageCryptoPresenter implements OnCryptoClickListener {
|
|||
this.messageCryptoMvpView = messageCryptoMvpView;
|
||||
}
|
||||
|
||||
public CryptoResultAnnotation getCryptoResultAnnotation() {
|
||||
return cryptoResultAnnotation;
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
if (reloadOnResumeWithoutRecreateFlag) {
|
||||
reloadOnResumeWithoutRecreateFlag = false;
|
||||
|
@ -96,23 +100,6 @@ public class MessageCryptoPresenter implements OnCryptoClickListener {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoClick() {
|
||||
if (cryptoResultAnnotation == null) {
|
||||
return;
|
||||
}
|
||||
MessageCryptoDisplayStatus displayStatus =
|
||||
MessageCryptoDisplayStatus.fromResultAnnotation(cryptoResultAnnotation);
|
||||
switch (displayStatus) {
|
||||
case LOADING:
|
||||
// no need to do anything, there is a progress bar...
|
||||
break;
|
||||
default:
|
||||
displayCryptoInfoDialog(displayStatus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedParameters") // for consistency with Activity.onActivityResult
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == REQUEST_CODE_UNKNOWN_KEY) {
|
||||
|
@ -128,11 +115,6 @@ public class MessageCryptoPresenter implements OnCryptoClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
private void displayCryptoInfoDialog(MessageCryptoDisplayStatus displayStatus) {
|
||||
messageCryptoMvpView.showCryptoInfoDialog(
|
||||
displayStatus, cryptoResultAnnotation.hasOpenPgpInsecureWarningPendingIntent());
|
||||
}
|
||||
|
||||
void onClickSearchKey() {
|
||||
try {
|
||||
PendingIntent pendingIntent = cryptoResultAnnotation.getOpenPgpSigningKeyIntentIfAny();
|
||||
|
@ -145,18 +127,6 @@ public class MessageCryptoPresenter implements OnCryptoClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
public void onClickShowCryptoKey() {
|
||||
try {
|
||||
PendingIntent pendingIntent = cryptoResultAnnotation.getOpenPgpSigningKeyIntentIfAny();
|
||||
if (pendingIntent != null) {
|
||||
messageCryptoMvpView.startPendingIntentForCryptoPresenter(
|
||||
pendingIntent.getIntentSender(), null, null, 0, 0, 0);
|
||||
}
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
Timber.e(e, "SendIntentException");
|
||||
}
|
||||
}
|
||||
|
||||
public void onClickRetryCryptoOperation() {
|
||||
messageCryptoMvpView.restartMessageCryptoProcessing();
|
||||
}
|
||||
|
@ -204,7 +174,6 @@ public class MessageCryptoPresenter implements OnCryptoClickListener {
|
|||
void startPendingIntentForCryptoPresenter(IntentSender si, Integer requestCode, Intent fillIntent,
|
||||
int flagsMask, int flagValues, int extraFlags) throws IntentSender.SendIntentException;
|
||||
|
||||
void showCryptoInfoDialog(MessageCryptoDisplayStatus displayStatus, boolean hasSecurityWarning);
|
||||
void showCryptoConfigDialog();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.fsck.k9.ui.messageview
|
||||
|
||||
interface MessageHeaderClickListener {
|
||||
fun onParticipantsContainerClick()
|
||||
fun onMenuItemClick(itemId: Int)
|
||||
}
|
|
@ -9,7 +9,7 @@ import android.graphics.drawable.Drawable;
|
|||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -51,10 +51,13 @@ public class MessageTopView extends LinearLayout {
|
|||
private ViewGroup containerView;
|
||||
private Button mDownloadRemainder;
|
||||
private AttachmentViewCallback attachmentCallback;
|
||||
private View extraHeaderContainer;
|
||||
private Button showPicturesButton;
|
||||
private boolean isShowingProgress;
|
||||
private boolean showPicturesButtonClicked;
|
||||
|
||||
private boolean showAccountChip;
|
||||
|
||||
private MessageCryptoPresenter messageCryptoPresenter;
|
||||
|
||||
|
||||
|
@ -76,6 +79,7 @@ public class MessageTopView extends LinearLayout {
|
|||
mDownloadRemainder = findViewById(R.id.download_remainder);
|
||||
mDownloadRemainder.setVisibility(View.GONE);
|
||||
|
||||
extraHeaderContainer = findViewById(R.id.extra_header_container);
|
||||
showPicturesButton = findViewById(R.id.show_pictures);
|
||||
setShowPicturesButtonListener();
|
||||
|
||||
|
@ -84,6 +88,10 @@ public class MessageTopView extends LinearLayout {
|
|||
hideHeaderView();
|
||||
}
|
||||
|
||||
public void setShowAccountChip(boolean showAccountChip) {
|
||||
this.showAccountChip = showAccountChip;
|
||||
}
|
||||
|
||||
private void setShowPicturesButtonListener() {
|
||||
showPicturesButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
|
@ -208,7 +216,7 @@ public class MessageTopView extends LinearLayout {
|
|||
}
|
||||
|
||||
public void setHeaders(Message message, Account account, boolean showStar) {
|
||||
mHeaderContainer.populate(message, account, showStar);
|
||||
mHeaderContainer.populate(message, account, showStar, showAccountChip);
|
||||
mHeaderContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
@ -220,8 +228,8 @@ public class MessageTopView extends LinearLayout {
|
|||
mHeaderContainer.setOnFlagListener(listener);
|
||||
}
|
||||
|
||||
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
|
||||
mHeaderContainer.setOnMenuItemClickListener(listener);
|
||||
public void setMessageHeaderClickListener(MessageHeaderClickListener listener) {
|
||||
mHeaderContainer.setMessageHeaderClickListener(listener);
|
||||
}
|
||||
|
||||
private void hideHeaderView() {
|
||||
|
@ -238,7 +246,6 @@ public class MessageTopView extends LinearLayout {
|
|||
|
||||
public void setMessageCryptoPresenter(MessageCryptoPresenter messageCryptoPresenter) {
|
||||
this.messageCryptoPresenter = messageCryptoPresenter;
|
||||
mHeaderContainer.setOnCryptoClickListener(messageCryptoPresenter);
|
||||
}
|
||||
|
||||
public void enableDownloadButton() {
|
||||
|
@ -259,11 +266,11 @@ public class MessageTopView extends LinearLayout {
|
|||
}
|
||||
|
||||
private void showShowPicturesButton() {
|
||||
showPicturesButton.setVisibility(View.VISIBLE);
|
||||
extraHeaderContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideShowPicturesButton() {
|
||||
showPicturesButton.setVisibility(View.GONE);
|
||||
extraHeaderContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private boolean shouldAutomaticallyLoadPictures(ShowPictures showPicturesSetting, Message message) {
|
||||
|
|
|
@ -29,6 +29,8 @@ class MessageViewContainerFragment : Fragment() {
|
|||
setMenuVisibility(value)
|
||||
}
|
||||
|
||||
private var showAccountChip: Boolean = true
|
||||
|
||||
lateinit var messageReference: MessageReference
|
||||
private set
|
||||
|
||||
|
@ -72,7 +74,9 @@ class MessageViewContainerFragment : Fragment() {
|
|||
lastDirection = savedInstanceState.getSerializable(STATE_LAST_DIRECTION) as Direction?
|
||||
}
|
||||
|
||||
adapter = MessageViewContainerAdapter(this)
|
||||
showAccountChip = arguments?.getBoolean(ARG_SHOW_ACCOUNT_CHIP) ?: showAccountChip
|
||||
|
||||
adapter = MessageViewContainerAdapter(this, showAccountChip)
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
|
@ -229,7 +233,11 @@ class MessageViewContainerFragment : Fragment() {
|
|||
findMessageViewFragment().onPendingIntentResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
private class MessageViewContainerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
|
||||
private class MessageViewContainerAdapter(
|
||||
fragment: Fragment,
|
||||
private val showAccountChip: Boolean
|
||||
) : FragmentStateAdapter(fragment) {
|
||||
|
||||
var messageList: List<MessageListItem> = emptyList()
|
||||
set(value) {
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
|
@ -257,7 +265,7 @@ class MessageViewContainerFragment : Fragment() {
|
|||
check(position in messageList.indices)
|
||||
|
||||
val messageReference = messageList[position].messageReference
|
||||
return MessageViewFragment.newInstance(messageReference)
|
||||
return MessageViewFragment.newInstance(messageReference, showAccountChip)
|
||||
}
|
||||
|
||||
fun getMessageReference(position: Int): MessageReference? {
|
||||
|
@ -298,13 +306,15 @@ class MessageViewContainerFragment : Fragment() {
|
|||
|
||||
companion object {
|
||||
private const val ARG_REFERENCE = "reference"
|
||||
private const val ARG_SHOW_ACCOUNT_CHIP = "showAccountChip"
|
||||
|
||||
private const val STATE_MESSAGE_REFERENCE = "messageReference"
|
||||
private const val STATE_LAST_DIRECTION = "lastDirection"
|
||||
|
||||
fun newInstance(reference: MessageReference): MessageViewContainerFragment {
|
||||
fun newInstance(reference: MessageReference, showAccountChip: Boolean): MessageViewContainerFragment {
|
||||
return MessageViewContainerFragment().withArguments(
|
||||
ARG_REFERENCE to reference.toIdentityString()
|
||||
ARG_REFERENCE to reference.toIdentityString(),
|
||||
ARG_SHOW_ACCOUNT_CHIP to showAccountChip
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.core.app.ActivityCompat
|
|||
import androidx.core.content.withStyledAttributes
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.activity.MessageCompose
|
||||
|
@ -45,13 +46,12 @@ import com.fsck.k9.ui.R
|
|||
import com.fsck.k9.ui.base.Theme
|
||||
import com.fsck.k9.ui.base.ThemeManager
|
||||
import com.fsck.k9.ui.choosefolder.ChooseFolderActivity
|
||||
import com.fsck.k9.ui.messagedetails.MessageDetailsFragment
|
||||
import com.fsck.k9.ui.messagesource.MessageSourceActivity
|
||||
import com.fsck.k9.ui.messageview.CryptoInfoDialog.OnClickShowCryptoKeyListener
|
||||
import com.fsck.k9.ui.messageview.MessageCryptoPresenter.MessageCryptoMvpView
|
||||
import com.fsck.k9.ui.settings.account.AccountSettingsActivity
|
||||
import com.fsck.k9.ui.share.ShareIntentBuilder
|
||||
import com.fsck.k9.ui.withArguments
|
||||
import com.fsck.k9.view.MessageCryptoDisplayStatus
|
||||
import java.util.Locale
|
||||
import org.koin.android.ext.android.inject
|
||||
import timber.log.Timber
|
||||
|
@ -59,8 +59,7 @@ import timber.log.Timber
|
|||
class MessageViewFragment :
|
||||
Fragment(),
|
||||
ConfirmationDialogFragmentListener,
|
||||
AttachmentViewCallback,
|
||||
OnClickShowCryptoKeyListener {
|
||||
AttachmentViewCallback {
|
||||
|
||||
private val themeManager: ThemeManager by inject()
|
||||
private val messageLoaderHelperFactory: MessageLoaderHelperFactory by inject()
|
||||
|
@ -86,6 +85,7 @@ class MessageViewFragment :
|
|||
|
||||
private lateinit var account: Account
|
||||
lateinit var messageReference: MessageReference
|
||||
private var showAccountChip: Boolean = true
|
||||
|
||||
private var currentAttachmentViewInfo: AttachmentViewInfo? = null
|
||||
private var isDeleteMenuItemDisabled: Boolean = false
|
||||
|
@ -118,6 +118,9 @@ class MessageViewFragment :
|
|||
messageReference = MessageReference.parse(arguments?.getString(ARG_REFERENCE))
|
||||
?: error("Invalid argument '$ARG_REFERENCE'")
|
||||
|
||||
showAccountChip = arguments?.getBoolean(ARG_SHOW_ACCOUNT_CHIP)
|
||||
?: error("Missing argument: '$ARG_SHOW_ACCOUNT_CHIP'")
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
wasMessageMarkedAsOpened = savedInstanceState.getBoolean(STATE_WAS_MESSAGE_MARKED_AS_OPENED)
|
||||
}
|
||||
|
@ -129,6 +132,8 @@ class MessageViewFragment :
|
|||
fragmentManager = parentFragmentManager,
|
||||
callback = messageLoaderCallbacks
|
||||
)
|
||||
|
||||
setFragmentResultListener(MessageDetailsFragment.FRAGMENT_RESULT_KEY, ::onMessageDetailsResult)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
|
@ -145,6 +150,8 @@ class MessageViewFragment :
|
|||
}
|
||||
|
||||
private fun initializeMessageTopView(messageTopView: MessageTopView) {
|
||||
messageTopView.setShowAccountChip(showAccountChip)
|
||||
|
||||
messageTopView.setAttachmentCallback(this)
|
||||
messageTopView.setMessageCryptoPresenter(messageCryptoPresenter)
|
||||
|
||||
|
@ -152,9 +159,7 @@ class MessageViewFragment :
|
|||
onToggleFlagged()
|
||||
}
|
||||
|
||||
messageTopView.setOnMenuItemClickListener { item ->
|
||||
onReplyMenuItemClicked(item.itemId)
|
||||
}
|
||||
messageTopView.setMessageHeaderClickListener(messageHeaderClickListener)
|
||||
|
||||
messageTopView.setOnDownloadButtonClickListener {
|
||||
onDownloadButtonClicked()
|
||||
|
@ -376,17 +381,23 @@ class MessageViewFragment :
|
|||
messageTopView.setSubject(displaySubject)
|
||||
}
|
||||
|
||||
private fun onReplyMenuItemClicked(itemId: Int): Boolean {
|
||||
when (itemId) {
|
||||
R.id.reply -> onReply()
|
||||
R.id.reply_all -> onReplyAll()
|
||||
R.id.forward -> onForward()
|
||||
R.id.forward_as_attachment -> onForwardAsAttachment()
|
||||
R.id.share -> onSendAlternate()
|
||||
else -> error("Missing handler for reply menu item $itemId")
|
||||
private val messageHeaderClickListener = object : MessageHeaderClickListener {
|
||||
override fun onParticipantsContainerClick() {
|
||||
val messageDetailsFragment = MessageDetailsFragment.create(messageReference)
|
||||
messageDetailsFragment.cryptoResult = messageCryptoPresenter.cryptoResultAnnotation
|
||||
messageDetailsFragment.show(parentFragmentManager, "message_details")
|
||||
}
|
||||
|
||||
return true
|
||||
override fun onMenuItemClick(itemId: Int) {
|
||||
when (itemId) {
|
||||
R.id.reply -> onReply()
|
||||
R.id.reply_all -> onReplyAll()
|
||||
R.id.forward -> onForward()
|
||||
R.id.forward_as_attachment -> onForwardAsAttachment()
|
||||
R.id.share -> onSendAlternate()
|
||||
else -> error("Missing handler for reply menu item $itemId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDownloadButtonClicked() {
|
||||
|
@ -568,6 +579,20 @@ class MessageViewFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun onMessageDetailsResult(requestKey: String, result: Bundle) {
|
||||
when (val action = result.getString(MessageDetailsFragment.RESULT_ACTION)) {
|
||||
MessageDetailsFragment.ACTION_SEARCH_KEYS -> {
|
||||
messageCryptoPresenter.onClickSearchKey()
|
||||
}
|
||||
MessageDetailsFragment.ACTION_SHOW_WARNING -> {
|
||||
messageCryptoPresenter.onClickShowCryptoWarningDetails()
|
||||
}
|
||||
else -> {
|
||||
error("Unsupported action: $action")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDocumentResult(data: Intent?) {
|
||||
if (data != null && data.data != null) {
|
||||
createAttachmentController(currentAttachmentViewInfo).saveAttachmentTo(data.data)
|
||||
|
@ -806,12 +831,6 @@ class MessageViewFragment :
|
|||
)
|
||||
}
|
||||
|
||||
override fun showCryptoInfoDialog(displayStatus: MessageCryptoDisplayStatus, hasSecurityWarning: Boolean) {
|
||||
val dialog = CryptoInfoDialog.newInstance(displayStatus, hasSecurityWarning)
|
||||
dialog.setTargetFragment(this@MessageViewFragment, 0)
|
||||
dialog.show(parentFragmentManager, "crypto_info_dialog")
|
||||
}
|
||||
|
||||
override fun restartMessageCryptoProcessing() {
|
||||
messageTopView.setToLoadingState()
|
||||
messageLoaderHelper.asyncRestartMessageCryptoProcessing()
|
||||
|
@ -822,18 +841,6 @@ class MessageViewFragment :
|
|||
}
|
||||
}
|
||||
|
||||
override fun onClickShowSecurityWarning() {
|
||||
messageCryptoPresenter.onClickShowCryptoWarningDetails()
|
||||
}
|
||||
|
||||
override fun onClickSearchKey() {
|
||||
messageCryptoPresenter.onClickSearchKey()
|
||||
}
|
||||
|
||||
override fun onClickShowCryptoKey() {
|
||||
messageCryptoPresenter.onClickShowCryptoKey()
|
||||
}
|
||||
|
||||
interface MessageViewFragmentListener {
|
||||
fun onForward(messageReference: MessageReference, decryptionResultForReply: Parcelable?)
|
||||
fun onForwardAsAttachment(messageReference: MessageReference, decryptionResultForReply: Parcelable?)
|
||||
|
@ -960,6 +967,7 @@ class MessageViewFragment :
|
|||
const val PROGRESS_THRESHOLD_MILLIS = 500 * 1000
|
||||
|
||||
private const val ARG_REFERENCE = "reference"
|
||||
private const val ARG_SHOW_ACCOUNT_CHIP = "showAccountChip"
|
||||
|
||||
private const val STATE_WAS_MESSAGE_MARKED_AS_OPENED = "wasMessageMarkedAsOpened"
|
||||
|
||||
|
@ -967,9 +975,10 @@ class MessageViewFragment :
|
|||
private const val ACTIVITY_CHOOSE_FOLDER_COPY = 2
|
||||
private const val REQUEST_CODE_CREATE_DOCUMENT = 3
|
||||
|
||||
fun newInstance(reference: MessageReference): MessageViewFragment {
|
||||
fun newInstance(reference: MessageReference, showAccountChip: Boolean): MessageViewFragment {
|
||||
return MessageViewFragment().withArguments(
|
||||
ARG_REFERENCE to reference.toIdentityString()
|
||||
ARG_REFERENCE to reference.toIdentityString(),
|
||||
ARG_SHOW_ACCOUNT_CHIP to showAccountChip
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package com.fsck.k9.ui.messageview;
|
||||
|
||||
|
||||
public interface OnCryptoClickListener {
|
||||
void onCryptoClick();
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package com.fsck.k9.ui.messageview
|
||||
|
||||
import android.text.SpannableStringBuilder
|
||||
|
||||
private const val LIST_SEPARATOR = ", "
|
||||
|
||||
/**
|
||||
* Calculates how many recipient names can be displayed given the available width.
|
||||
*
|
||||
* We display up to [maxNumberOfRecipientNames] recipient names, then the number of additional recipients.
|
||||
*
|
||||
* Example:
|
||||
* to me, Alice, Bob, Charly, Dora +11
|
||||
*
|
||||
* If there's not enough room to display the first recipient name, we return it anyway and expect the component that is
|
||||
* actually rendering the text to ellipsize [RecipientLayoutData.recipientNames], but not
|
||||
* [RecipientLayoutData.additionalRecipients].
|
||||
*/
|
||||
internal class RecipientLayoutCreator(
|
||||
private val textMeasure: TextMeasure,
|
||||
private val maxNumberOfRecipientNames: Int,
|
||||
private val recipientsPrefix: String,
|
||||
private val additionalRecipientSpacing: Int,
|
||||
private val additionalRecipientsPrefix: String
|
||||
) {
|
||||
fun createRecipientLayout(
|
||||
recipientNames: List<CharSequence>,
|
||||
totalNumberOfRecipients: Int,
|
||||
availableWidth: Int
|
||||
): RecipientLayoutData {
|
||||
require(recipientNames.isNotEmpty())
|
||||
|
||||
val displayRecipientsBuilder = SpannableStringBuilder()
|
||||
|
||||
if (recipientNames.size == 1) {
|
||||
displayRecipientsBuilder.append(recipientsPrefix)
|
||||
displayRecipientsBuilder.append(recipientNames.first())
|
||||
|
||||
return RecipientLayoutData(
|
||||
recipientNames = displayRecipientsBuilder,
|
||||
additionalRecipients = null
|
||||
)
|
||||
}
|
||||
|
||||
val additionalRecipientsBuilder = StringBuilder(additionalRecipientsPrefix + 10)
|
||||
|
||||
val maxRecipientNames = recipientNames.size.coerceAtMost(maxNumberOfRecipientNames)
|
||||
for (numberOfDisplayRecipients in maxRecipientNames downTo 2) {
|
||||
displayRecipientsBuilder.clear()
|
||||
displayRecipientsBuilder.append(recipientsPrefix)
|
||||
|
||||
recipientNames.asSequence()
|
||||
.take(numberOfDisplayRecipients)
|
||||
.joinTo(displayRecipientsBuilder, separator = LIST_SEPARATOR)
|
||||
|
||||
additionalRecipientsBuilder.setLength(0)
|
||||
val numberOfAdditionalRecipients = totalNumberOfRecipients - numberOfDisplayRecipients
|
||||
if (numberOfAdditionalRecipients > 0) {
|
||||
additionalRecipientsBuilder.append(additionalRecipientsPrefix)
|
||||
additionalRecipientsBuilder.append(numberOfAdditionalRecipients)
|
||||
}
|
||||
|
||||
if (doesTextFitAvailableWidth(displayRecipientsBuilder, additionalRecipientsBuilder, availableWidth)) {
|
||||
return RecipientLayoutData(
|
||||
recipientNames = displayRecipientsBuilder,
|
||||
additionalRecipients = additionalRecipientsBuilder.toStringOrNull()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
displayRecipientsBuilder.clear()
|
||||
displayRecipientsBuilder.append(recipientsPrefix)
|
||||
displayRecipientsBuilder.append(recipientNames.first())
|
||||
|
||||
return RecipientLayoutData(
|
||||
recipientNames = displayRecipientsBuilder,
|
||||
additionalRecipients = "$additionalRecipientsPrefix${totalNumberOfRecipients - 1}"
|
||||
)
|
||||
}
|
||||
|
||||
private fun doesTextFitAvailableWidth(
|
||||
displayRecipients: CharSequence,
|
||||
additionalRecipients: CharSequence,
|
||||
availableWidth: Int
|
||||
): Boolean {
|
||||
val recipientNamesWidth = textMeasure.measureRecipientNames(displayRecipients)
|
||||
if (recipientNamesWidth > availableWidth) {
|
||||
return false
|
||||
} else if (additionalRecipients.isEmpty()) {
|
||||
return true
|
||||
}
|
||||
|
||||
val totalWidth = recipientNamesWidth + additionalRecipientSpacing +
|
||||
textMeasure.measureRecipientCount(additionalRecipients)
|
||||
|
||||
return totalWidth <= availableWidth
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringBuilder.toStringOrNull(): String? {
|
||||
return if (isEmpty()) null else toString()
|
||||
}
|
||||
|
||||
internal data class RecipientLayoutData(
|
||||
val recipientNames: CharSequence,
|
||||
val additionalRecipients: String?
|
||||
)
|
||||
|
||||
internal interface TextMeasure {
|
||||
/**
|
||||
* Measure the width of the supplied recipient names when rendered.
|
||||
*/
|
||||
fun measureRecipientNames(text: CharSequence): Int
|
||||
|
||||
/**
|
||||
* Measure the width of the supplied recipient count when rendered.
|
||||
*/
|
||||
fun measureRecipientCount(text: CharSequence): Int
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
package com.fsck.k9.ui.messageview
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isGone
|
||||
import com.fsck.k9.ui.R
|
||||
|
||||
private const val MAX_NUMBER_OF_RECIPIENT_NAMES = 5
|
||||
|
||||
/**
|
||||
* View that displays the names of recipients of a message.
|
||||
*
|
||||
* Up to [MAX_NUMBER_OF_RECIPIENT_NAMES] names of recipients are displayed, followed by the number of recipients that
|
||||
* weren't displayed.
|
||||
*
|
||||
* Examples:
|
||||
* - to me, Alice, Bob, Charly +3
|
||||
* - to Camila Hyphenated-Nam… +5
|
||||
*
|
||||
* This custom layout uses [RecipientLayoutCreator] to figure out how many recipient names can be displayed without
|
||||
* being truncated. If not even one recipient name can be displayed without being truncated, we first measure the space
|
||||
* needed for number of additional recipients, then use the rest to display the first recipient and ellipsize the end.
|
||||
*/
|
||||
class RecipientNamesView(context: Context, attrs: AttributeSet?) : ViewGroup(context, attrs) {
|
||||
val maxNumberOfRecipientNames: Int = MAX_NUMBER_OF_RECIPIENT_NAMES
|
||||
|
||||
private val recipientLayoutCreator: RecipientLayoutCreator
|
||||
|
||||
private val recipientNameTextView: TextView
|
||||
private val recipientCountTextView: TextView
|
||||
private val additionRecipientSpacing: Int
|
||||
|
||||
init {
|
||||
LayoutInflater.from(context).inflate(R.layout.recipient_names, this, true)
|
||||
recipientNameTextView = findViewById(R.id.recipient_names)
|
||||
recipientCountTextView = findViewById(R.id.recipient_count)
|
||||
additionRecipientSpacing = (recipientCountTextView.layoutParams as MarginLayoutParams).marginStart
|
||||
}
|
||||
|
||||
private var recipientNames: List<CharSequence> = emptyList()
|
||||
private var numberOfRecipients: Int = 0
|
||||
|
||||
private val textMeasure = object : TextMeasure {
|
||||
override fun measureRecipientNames(text: CharSequence): Int {
|
||||
return measureWidth(recipientNameTextView, text)
|
||||
}
|
||||
|
||||
override fun measureRecipientCount(text: CharSequence): Int {
|
||||
return measureWidth(recipientCountTextView, text)
|
||||
}
|
||||
|
||||
private fun measureWidth(textView: TextView, text: CharSequence): Int {
|
||||
textView.text = text
|
||||
|
||||
val widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
||||
val heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)
|
||||
textView.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
return textView.measuredWidth
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
recipientLayoutCreator = RecipientLayoutCreator(
|
||||
textMeasure = textMeasure,
|
||||
maxNumberOfRecipientNames = MAX_NUMBER_OF_RECIPIENT_NAMES,
|
||||
recipientsPrefix = context.getString(R.string.message_view_recipient_prefix),
|
||||
additionalRecipientSpacing = additionRecipientSpacing,
|
||||
additionalRecipientsPrefix = context.getString(R.string.message_view_additional_recipient_prefix)
|
||||
)
|
||||
|
||||
if (isInEditMode) {
|
||||
recipientNames = listOf(
|
||||
"Grace Hopper", "Katherine Johnson", "Margaret Hamilton", "Adele Goldberg", "Steve Shirley"
|
||||
)
|
||||
numberOfRecipients = 8
|
||||
}
|
||||
}
|
||||
|
||||
fun setTextSize(textSize: Int) {
|
||||
recipientNameTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize.toFloat())
|
||||
recipientCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize.toFloat())
|
||||
}
|
||||
|
||||
fun setRecipients(recipientNames: List<CharSequence>, numberOfRecipients: Int) {
|
||||
if (recipientNames != this.recipientNames && numberOfRecipients != this.numberOfRecipients) {
|
||||
this.recipientNames = recipientNames
|
||||
this.numberOfRecipients = numberOfRecipients
|
||||
requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
require(MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
|
||||
"Width of RecipientNamesView needs to be constrained"
|
||||
}
|
||||
|
||||
recipientNameTextView.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
recipientCountTextView.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val height = maxOf(recipientNameTextView.measuredHeight, recipientCountTextView.measuredHeight)
|
||||
setMeasuredDimension(width, height)
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
val availableWidth = width
|
||||
|
||||
val recipientLayoutData = recipientLayoutCreator.createRecipientLayout(
|
||||
recipientNames, numberOfRecipients, availableWidth
|
||||
)
|
||||
|
||||
recipientNameTextView.text = recipientLayoutData.recipientNames
|
||||
val additionalRecipientsVisible = recipientLayoutData.additionalRecipients != null
|
||||
val remainingWidth: Int
|
||||
if (additionalRecipientsVisible) {
|
||||
recipientCountTextView.isGone = false
|
||||
recipientCountTextView.text = recipientLayoutData.additionalRecipients
|
||||
|
||||
recipientCountTextView.measure(
|
||||
MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
|
||||
MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.AT_MOST)
|
||||
)
|
||||
|
||||
remainingWidth = availableWidth - additionRecipientSpacing - recipientCountTextView.measuredWidth
|
||||
} else {
|
||||
recipientCountTextView.isGone = true
|
||||
remainingWidth = availableWidth
|
||||
}
|
||||
|
||||
recipientNameTextView.measure(
|
||||
MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST),
|
||||
MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.AT_MOST)
|
||||
)
|
||||
|
||||
if (layoutDirection == LAYOUT_DIRECTION_LTR) {
|
||||
val recipientNameRight = recipientNameTextView.measuredWidth
|
||||
recipientNameTextView.layout(
|
||||
0,
|
||||
0,
|
||||
recipientNameRight,
|
||||
recipientNameTextView.measuredHeight
|
||||
)
|
||||
val recipientCountLeft = recipientNameRight + additionRecipientSpacing
|
||||
recipientCountTextView.layout(
|
||||
recipientCountLeft,
|
||||
0,
|
||||
recipientCountLeft + recipientCountTextView.measuredWidth,
|
||||
recipientCountTextView.measuredHeight
|
||||
)
|
||||
} else {
|
||||
val recipientNameLeft = width - recipientNameTextView.measuredWidth
|
||||
recipientNameTextView.layout(
|
||||
recipientNameLeft,
|
||||
0,
|
||||
right,
|
||||
recipientNameTextView.measuredHeight
|
||||
)
|
||||
val recipientCountRight = recipientNameLeft - additionRecipientSpacing
|
||||
recipientCountTextView.layout(
|
||||
recipientCountRight - recipientCountTextView.measuredWidth,
|
||||
0,
|
||||
recipientCountRight,
|
||||
0 + recipientCountTextView.measuredHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkLayoutParams(p: LayoutParams?): Boolean {
|
||||
return p is MarginLayoutParams
|
||||
}
|
||||
|
||||
override fun generateDefaultLayoutParams(): LayoutParams {
|
||||
return MarginLayoutParams(0, 0)
|
||||
}
|
||||
|
||||
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
|
||||
return MarginLayoutParams(context, attrs)
|
||||
}
|
||||
}
|
|
@ -117,12 +117,9 @@ class GeneralSettingsDataStore(
|
|||
"message_list_date_font" -> K9.fontSizes.messageListDate.toString()
|
||||
"message_list_preview_font" -> K9.fontSizes.messageListPreview.toString()
|
||||
"message_view_sender_font" -> K9.fontSizes.messageViewSender.toString()
|
||||
"message_view_to_font" -> K9.fontSizes.messageViewTo.toString()
|
||||
"message_view_cc_font" -> K9.fontSizes.messageViewCC.toString()
|
||||
"message_view_bcc_font" -> K9.fontSizes.messageViewBCC.toString()
|
||||
"message_view_recipients_font" -> K9.fontSizes.messageViewRecipients.toString()
|
||||
"message_view_subject_font" -> K9.fontSizes.messageViewSubject.toString()
|
||||
"message_view_date_font" -> K9.fontSizes.messageViewDate.toString()
|
||||
"message_view_additional_headers_font" -> K9.fontSizes.messageViewAdditionalHeaders.toString()
|
||||
"message_compose_input_font" -> K9.fontSizes.messageComposeInput.toString()
|
||||
"swipe_action_right" -> swipeActionToString(K9.swipeRightAction)
|
||||
"swipe_action_left" -> swipeActionToString(K9.swipeLeftAction)
|
||||
|
@ -154,12 +151,9 @@ class GeneralSettingsDataStore(
|
|||
"message_list_date_font" -> K9.fontSizes.messageListDate = value.toInt()
|
||||
"message_list_preview_font" -> K9.fontSizes.messageListPreview = value.toInt()
|
||||
"message_view_sender_font" -> K9.fontSizes.messageViewSender = value.toInt()
|
||||
"message_view_to_font" -> K9.fontSizes.messageViewTo = value.toInt()
|
||||
"message_view_cc_font" -> K9.fontSizes.messageViewCC = value.toInt()
|
||||
"message_view_bcc_font" -> K9.fontSizes.messageViewBCC = value.toInt()
|
||||
"message_view_recipients_font" -> K9.fontSizes.messageViewRecipients = value.toInt()
|
||||
"message_view_subject_font" -> K9.fontSizes.messageViewSubject = value.toInt()
|
||||
"message_view_date_font" -> K9.fontSizes.messageViewDate = value.toInt()
|
||||
"message_view_additional_headers_font" -> K9.fontSizes.messageViewAdditionalHeaders = value.toInt()
|
||||
"message_compose_input_font" -> K9.fontSizes.messageComposeInput = value.toInt()
|
||||
"swipe_action_right" -> K9.swipeRightAction = stringToSwipeAction(value)
|
||||
"swipe_action_left" -> K9.swipeLeftAction = stringToSwipeAction(value)
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
package com.fsck.k9.view
|
||||
|
||||
import com.fsck.k9.helper.ReplyToParser
|
||||
import com.fsck.k9.message.ReplyActionStrategy
|
||||
import com.fsck.k9.ui.helper.RelativeDateTimeFormatter
|
||||
import org.koin.dsl.module
|
||||
|
||||
val viewModule = module {
|
||||
single { WebViewConfigProvider(get()) }
|
||||
single { WebViewConfigProvider(themeManager = get()) }
|
||||
factory { RelativeDateTimeFormatter(context = get(), clock = get()) }
|
||||
factory { ReplyToParser() }
|
||||
factory { ReplyActionStrategy(replyRoParser = get()) }
|
||||
}
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
package com.fsck.k9.view;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener;
|
||||
import androidx.appcompat.widget.TooltipCompat;
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.DI;
|
||||
import com.fsck.k9.FontSizes;
|
||||
|
@ -28,112 +27,90 @@ import com.fsck.k9.contacts.ContactPictureLoader;
|
|||
import com.fsck.k9.helper.ClipboardManager;
|
||||
import com.fsck.k9.helper.Contacts;
|
||||
import com.fsck.k9.helper.MessageHelper;
|
||||
import com.fsck.k9.helper.RealAddressFormatter;
|
||||
import com.fsck.k9.helper.RealContactNameProvider;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Flag;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.ui.ContactBadge;
|
||||
import com.fsck.k9.message.ReplyAction;
|
||||
import com.fsck.k9.message.ReplyActionStrategy;
|
||||
import com.fsck.k9.message.ReplyActions;
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.ui.messageview.OnCryptoClickListener;
|
||||
import timber.log.Timber;
|
||||
import com.fsck.k9.ui.helper.RelativeDateTimeFormatter;
|
||||
import com.fsck.k9.ui.messageview.DisplayRecipients;
|
||||
import com.fsck.k9.ui.messageview.DisplayRecipientsExtractor;
|
||||
import com.fsck.k9.ui.messageview.MessageHeaderClickListener;
|
||||
import com.fsck.k9.ui.messageview.RecipientNamesView;
|
||||
import com.google.android.material.chip.Chip;
|
||||
|
||||
|
||||
public class MessageHeader extends LinearLayout implements OnClickListener, OnLongClickListener {
|
||||
private static final int DEFAULT_SUBJECT_LINES = 3;
|
||||
|
||||
private final ClipboardManager clipboardManager = DI.get(ClipboardManager.class);
|
||||
private final ReplyActionStrategy replyActionStrategy = DI.get(ReplyActionStrategy.class);
|
||||
private final FontSizes fontSizes = K9.getFontSizes();
|
||||
|
||||
private Context mContext;
|
||||
private TextView mFromView;
|
||||
private TextView mSenderView;
|
||||
private TextView mDateView;
|
||||
private TextView mToView;
|
||||
private TextView mToLabel;
|
||||
private TextView mCcView;
|
||||
private TextView mCcLabel;
|
||||
private TextView mBccView;
|
||||
private TextView mBccLabel;
|
||||
private TextView mSubjectView;
|
||||
private ImageView mCryptoStatusIcon;
|
||||
private Chip accountChip;
|
||||
private TextView subjectView;
|
||||
private ImageView starView;
|
||||
private ImageView contactPictureView;
|
||||
private TextView fromView;
|
||||
private ImageView cryptoStatusIcon;
|
||||
private RecipientNamesView recipientNamesView;
|
||||
private TextView dateView;
|
||||
private ImageView menuPrimaryActionView;
|
||||
|
||||
private View mChip;
|
||||
private CheckBox mFlagged;
|
||||
private int defaultSubjectColor;
|
||||
private View singleMessageOptionIcon;
|
||||
private View mAnsweredIcon;
|
||||
private View mForwardedIcon;
|
||||
private Message mMessage;
|
||||
private Account mAccount;
|
||||
private FontSizes mFontSizes = K9.getFontSizes();
|
||||
private Contacts mContacts;
|
||||
private MessageHelper messageHelper;
|
||||
private RelativeDateTimeFormatter relativeDateTimeFormatter;
|
||||
|
||||
private MessageHelper mMessageHelper;
|
||||
private ContactPictureLoader mContactsPictureLoader;
|
||||
private ContactBadge mContactBadge;
|
||||
|
||||
private OnCryptoClickListener onCryptoClickListener;
|
||||
private OnMenuItemClickListener onMenuItemClickListener;
|
||||
private MessageHeaderClickListener messageHeaderClickListener;
|
||||
private ReplyActions replyActions;
|
||||
|
||||
|
||||
public MessageHeader(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
mContacts = Contacts.getInstance(mContext);
|
||||
|
||||
if (!isInEditMode()) {
|
||||
messageHelper = MessageHelper.getInstance(getContext());
|
||||
relativeDateTimeFormatter = DI.get(RelativeDateTimeFormatter.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
mAnsweredIcon = findViewById(R.id.answered);
|
||||
mForwardedIcon = findViewById(R.id.forwarded);
|
||||
mFromView = findViewById(R.id.from);
|
||||
mSenderView = findViewById(R.id.sender);
|
||||
mToView = findViewById(R.id.to);
|
||||
mToLabel = findViewById(R.id.to_label);
|
||||
mCcView = findViewById(R.id.cc);
|
||||
mCcLabel = findViewById(R.id.cc_label);
|
||||
mBccView = findViewById(R.id.bcc);
|
||||
mBccLabel = findViewById(R.id.bcc_label);
|
||||
accountChip = findViewById(R.id.chip);
|
||||
subjectView = findViewById(R.id.subject);
|
||||
starView = findViewById(R.id.flagged);
|
||||
contactPictureView = findViewById(R.id.contact_picture);
|
||||
fromView = findViewById(R.id.from);
|
||||
cryptoStatusIcon = findViewById(R.id.crypto_status_icon);
|
||||
recipientNamesView = findViewById(R.id.recipients);
|
||||
dateView = findViewById(R.id.date);
|
||||
|
||||
mContactBadge = findViewById(R.id.contact_badge);
|
||||
fontSizes.setViewTextSize(subjectView, fontSizes.getMessageViewSubject());
|
||||
fontSizes.setViewTextSize(dateView, fontSizes.getMessageViewDate());
|
||||
fontSizes.setViewTextSize(fromView, fontSizes.getMessageViewSender());
|
||||
|
||||
singleMessageOptionIcon = findViewById(R.id.icon_single_message_options);
|
||||
int recipientTextSize = fontSizes.getMessageViewRecipients();
|
||||
if (recipientTextSize != FontSizes.FONT_DEFAULT) {
|
||||
recipientNamesView.setTextSize(recipientTextSize);
|
||||
}
|
||||
|
||||
mSubjectView = findViewById(R.id.subject);
|
||||
mChip = findViewById(R.id.chip);
|
||||
mDateView = findViewById(R.id.date);
|
||||
mFlagged = findViewById(R.id.flagged);
|
||||
subjectView.setOnClickListener(this);
|
||||
subjectView.setOnLongClickListener(this);
|
||||
|
||||
defaultSubjectColor = mSubjectView.getCurrentTextColor();
|
||||
mFontSizes.setViewTextSize(mSubjectView, mFontSizes.getMessageViewSubject());
|
||||
mFontSizes.setViewTextSize(mDateView, mFontSizes.getMessageViewDate());
|
||||
menuPrimaryActionView = findViewById(R.id.menu_primary_action);
|
||||
menuPrimaryActionView.setOnClickListener(this);
|
||||
|
||||
mFontSizes.setViewTextSize(mFromView, mFontSizes.getMessageViewSender());
|
||||
mFontSizes.setViewTextSize(mToView, mFontSizes.getMessageViewTo());
|
||||
mFontSizes.setViewTextSize(mToLabel, mFontSizes.getMessageViewTo());
|
||||
mFontSizes.setViewTextSize(mCcView, mFontSizes.getMessageViewCC());
|
||||
mFontSizes.setViewTextSize(mCcLabel, mFontSizes.getMessageViewCC());
|
||||
mFontSizes.setViewTextSize(mBccView, mFontSizes.getMessageViewBCC());
|
||||
mFontSizes.setViewTextSize(mBccLabel, mFontSizes.getMessageViewBCC());
|
||||
View menuOverflowView = findViewById(R.id.menu_overflow);
|
||||
menuOverflowView.setOnClickListener(this);
|
||||
String menuOverflowDescription =
|
||||
getContext().getString(androidx.appcompat.R.string.abc_action_menu_overflow_description);
|
||||
TooltipCompat.setTooltipText(menuOverflowView, menuOverflowDescription);
|
||||
|
||||
singleMessageOptionIcon.setOnClickListener(this);
|
||||
|
||||
mSubjectView.setOnClickListener(this);
|
||||
mFromView.setOnClickListener(this);
|
||||
mToView.setOnClickListener(this);
|
||||
mCcView.setOnClickListener(this);
|
||||
mBccView.setOnClickListener(this);
|
||||
|
||||
mSubjectView.setOnLongClickListener(this);
|
||||
mFromView.setOnLongClickListener(this);
|
||||
mToView.setOnLongClickListener(this);
|
||||
mCcView.setOnLongClickListener(this);
|
||||
mBccView.setOnLongClickListener(this);
|
||||
|
||||
mCryptoStatusIcon = findViewById(R.id.crypto_status_icon);
|
||||
mCryptoStatusIcon.setOnClickListener(this);
|
||||
|
||||
mMessageHelper = MessageHelper.getInstance(mContext);
|
||||
findViewById(R.id.participants_container).setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -141,168 +118,217 @@ public class MessageHeader extends LinearLayout implements OnClickListener, OnLo
|
|||
int id = view.getId();
|
||||
if (id == R.id.subject) {
|
||||
toggleSubjectViewMaxLines();
|
||||
} else if (id == R.id.from) {
|
||||
onAddSenderToContacts();
|
||||
} else if (id == R.id.to || id == R.id.cc || id == R.id.bcc) {
|
||||
expand((TextView)view, ((TextView)view).getEllipsize() != null);
|
||||
} else if (id == R.id.crypto_status_icon) {
|
||||
onCryptoClickListener.onCryptoClick();
|
||||
} else if (id == R.id.icon_single_message_options) {
|
||||
PopupMenu popupMenu = new PopupMenu(getContext(), view);
|
||||
popupMenu.setOnMenuItemClickListener(onMenuItemClickListener);
|
||||
popupMenu.inflate(R.menu.single_message_options);
|
||||
popupMenu.show();
|
||||
} else if (id == R.id.menu_primary_action) {
|
||||
performPrimaryReplyAction();
|
||||
} else if (id == R.id.menu_overflow) {
|
||||
showOverflowMenu(view);
|
||||
} else if (id == R.id.participants_container) {
|
||||
messageHeaderClickListener.onParticipantsContainerClick();
|
||||
}
|
||||
}
|
||||
|
||||
private void performPrimaryReplyAction() {
|
||||
ReplyAction defaultAction = replyActions.getDefaultAction();
|
||||
if (defaultAction == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (defaultAction) {
|
||||
case REPLY: {
|
||||
messageHeaderClickListener.onMenuItemClick(R.id.reply);
|
||||
break;
|
||||
}
|
||||
case REPLY_ALL: {
|
||||
messageHeaderClickListener.onMenuItemClick(R.id.reply_all);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException("Unknown reply action: " + defaultAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showOverflowMenu(View view) {
|
||||
PopupMenu popupMenu = new PopupMenu(getContext(), view);
|
||||
popupMenu.setOnMenuItemClickListener(item -> {
|
||||
messageHeaderClickListener.onMenuItemClick(item.getItemId());
|
||||
return true;
|
||||
});
|
||||
popupMenu.inflate(R.menu.single_message_options);
|
||||
setAdditionalReplyActions(popupMenu);
|
||||
popupMenu.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
int id = view.getId();
|
||||
|
||||
if (id == R.id.subject) {
|
||||
onAddSubjectToClipboard(mSubjectView.getText().toString());
|
||||
} else if (id == R.id.from) {
|
||||
onAddAddressesToClipboard(mMessage.getFrom());
|
||||
} else if (id == R.id.to) {
|
||||
onAddRecipientsToClipboard(Message.RecipientType.TO);
|
||||
} else if (id == R.id.cc) {
|
||||
onAddRecipientsToClipboard(Message.RecipientType.CC);
|
||||
onAddSubjectToClipboard(subjectView.getText().toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void toggleSubjectViewMaxLines() {
|
||||
if (mSubjectView.getMaxLines() == DEFAULT_SUBJECT_LINES) {
|
||||
mSubjectView.setMaxLines(Integer.MAX_VALUE);
|
||||
if (subjectView.getMaxLines() == DEFAULT_SUBJECT_LINES) {
|
||||
subjectView.setMaxLines(Integer.MAX_VALUE);
|
||||
} else {
|
||||
mSubjectView.setMaxLines(DEFAULT_SUBJECT_LINES);
|
||||
subjectView.setMaxLines(DEFAULT_SUBJECT_LINES);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAddSubjectToClipboard(String subject) {
|
||||
ClipboardManager clipboardManager = DI.get(ClipboardManager.class);
|
||||
clipboardManager.setText("subject", subject);
|
||||
|
||||
Toast.makeText(mContext, createMessageForSubject(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void onAddSenderToContacts() {
|
||||
if (mMessage != null) {
|
||||
try {
|
||||
final Address senderEmail = mMessage.getFrom()[0];
|
||||
mContacts.createContact(senderEmail);
|
||||
} catch (Exception e) {
|
||||
Timber.e(e, "Couldn't create contact");
|
||||
}
|
||||
}
|
||||
Toast.makeText(getContext(), createMessageForSubject(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public String createMessageForSubject() {
|
||||
return mContext.getResources().getString(R.string.copy_subject_to_clipboard);
|
||||
}
|
||||
|
||||
public String createMessage(int addressesCount) {
|
||||
return mContext.getResources().getQuantityString(R.plurals.copy_address_to_clipboard, addressesCount);
|
||||
}
|
||||
|
||||
private void onAddAddressesToClipboard(Address[] addresses) {
|
||||
String addressList = Address.toString(addresses);
|
||||
clipboardManager.setText("addresses", addressList);
|
||||
|
||||
Toast.makeText(mContext, createMessage(addresses.length), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void onAddRecipientsToClipboard(Message.RecipientType recipientType) {
|
||||
onAddAddressesToClipboard(mMessage.getRecipients(recipientType));
|
||||
return getResources().getString(R.string.copy_subject_to_clipboard);
|
||||
}
|
||||
|
||||
public void setOnFlagListener(OnClickListener listener) {
|
||||
mFlagged.setOnClickListener(listener);
|
||||
starView.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
public void populate(final Message message, final Account account, boolean showStar) {
|
||||
public void populate(final Message message, final Account account, boolean showStar, boolean showAccountChip) {
|
||||
if (showAccountChip) {
|
||||
accountChip.setVisibility(View.VISIBLE);
|
||||
accountChip.setText(account.getDisplayName());
|
||||
accountChip.setChipBackgroundColor(ColorStateList.valueOf(account.getChipColor()));
|
||||
} else {
|
||||
accountChip.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Address fromAddress = null;
|
||||
Address[] fromAddresses = message.getFrom();
|
||||
if (fromAddresses.length > 0) {
|
||||
fromAddress = fromAddresses[0];
|
||||
}
|
||||
|
||||
final Contacts contacts = K9.isShowContactName() ? mContacts : null;
|
||||
final CharSequence from = mMessageHelper.getSenderDisplayName(fromAddress);
|
||||
final CharSequence to = MessageHelper.toFriendly(message.getRecipients(Message.RecipientType.TO), contacts);
|
||||
final CharSequence cc = MessageHelper.toFriendly(message.getRecipients(Message.RecipientType.CC), contacts);
|
||||
final CharSequence bcc = MessageHelper.toFriendly(message.getRecipients(Message.RecipientType.BCC), contacts);
|
||||
|
||||
mMessage = message;
|
||||
mAccount = account;
|
||||
|
||||
if (K9.isShowContactPicture()) {
|
||||
mContactBadge.setVisibility(View.VISIBLE);
|
||||
mContactsPictureLoader = ContactPicture.getContactPictureLoader();
|
||||
} else {
|
||||
mContactBadge.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (shouldShowSender(message)) {
|
||||
mSenderView.setVisibility(VISIBLE);
|
||||
String sender = getResources().getString(R.string.message_view_sender_label,
|
||||
MessageHelper.toFriendly(message.getSender(), contacts));
|
||||
mSenderView.setText(sender);
|
||||
} else {
|
||||
mSenderView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
String dateTime = DateUtils.formatDateTime(mContext,
|
||||
message.getSentDate().getTime(),
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
| DateUtils.FORMAT_ABBREV_ALL
|
||||
| DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_SHOW_YEAR);
|
||||
mDateView.setText(dateTime);
|
||||
|
||||
if (K9.isShowContactPicture()) {
|
||||
contactPictureView.setVisibility(View.VISIBLE);
|
||||
if (fromAddress != null) {
|
||||
mContactBadge.setContact(fromAddress);
|
||||
mContactsPictureLoader.setContactPicture(mContactBadge, fromAddress);
|
||||
ContactPictureLoader contactsPictureLoader = ContactPicture.getContactPictureLoader();
|
||||
contactsPictureLoader.setContactPicture(contactPictureView, fromAddress);
|
||||
} else {
|
||||
mContactBadge.setImageResource(R.drawable.ic_contact_picture);
|
||||
contactPictureView.setImageResource(R.drawable.ic_contact_picture);
|
||||
}
|
||||
} else {
|
||||
contactPictureView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mFromView.setText(from);
|
||||
|
||||
updateAddressField(mToView, to, mToLabel);
|
||||
updateAddressField(mCcView, cc, mCcLabel);
|
||||
updateAddressField(mBccView, bcc, mBccLabel);
|
||||
mAnsweredIcon.setVisibility(message.isSet(Flag.ANSWERED) ? View.VISIBLE : View.GONE);
|
||||
mForwardedIcon.setVisibility(message.isSet(Flag.FORWARDED) ? View.VISIBLE : View.GONE);
|
||||
CharSequence from = messageHelper.getSenderDisplayName(fromAddress);
|
||||
fromView.setText(from);
|
||||
|
||||
if (showStar) {
|
||||
mFlagged.setVisibility(View.VISIBLE);
|
||||
mFlagged.setChecked(message.isSet(Flag.FLAGGED));
|
||||
starView.setVisibility(View.VISIBLE);
|
||||
starView.setSelected(message.isSet(Flag.FLAGGED));
|
||||
} else {
|
||||
mFlagged.setVisibility(View.GONE);
|
||||
starView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mChip.setBackgroundColor(mAccount.getChipColor());
|
||||
if (message.getSentDate() != null) {
|
||||
dateView.setText(relativeDateTimeFormatter.formatDate(message.getSentDate().getTime()));
|
||||
} else {
|
||||
dateView.setText("");
|
||||
}
|
||||
|
||||
setRecipientNames(message, account);
|
||||
|
||||
setReplyActions(message, account);
|
||||
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void setSubject(@NonNull String subject) {
|
||||
mSubjectView.setText(subject);
|
||||
mSubjectView.setTextColor(0xff000000 | defaultSubjectColor);
|
||||
private void setRecipientNames(Message message, Account account) {
|
||||
Integer contactNameColor = K9.isChangeContactNameColor() ? K9.getContactNameColor() : null;
|
||||
|
||||
RealContactNameProvider contactNameProvider = new RealContactNameProvider(Contacts.getInstance(getContext()));
|
||||
|
||||
RealAddressFormatter addressFormatter = new RealAddressFormatter(contactNameProvider, account,
|
||||
K9.isShowCorrespondentNames(), K9.isShowContactName(), contactNameColor,
|
||||
getContext().getString(R.string.message_view_me_text));
|
||||
|
||||
DisplayRecipientsExtractor displayRecipientsExtractor = new DisplayRecipientsExtractor(addressFormatter,
|
||||
recipientNamesView.getMaxNumberOfRecipientNames());
|
||||
|
||||
DisplayRecipients displayRecipients = displayRecipientsExtractor.extractDisplayRecipients(message, account);
|
||||
|
||||
recipientNamesView.setRecipients(displayRecipients.getRecipientNames(),
|
||||
displayRecipients.getNumberOfRecipients());
|
||||
}
|
||||
|
||||
public static boolean shouldShowSender(Message message) {
|
||||
Address[] from = message.getFrom();
|
||||
Address[] sender = message.getSender();
|
||||
private void setReplyActions(Message message, Account account) {
|
||||
ReplyActions replyActions = replyActionStrategy.getReplyActions(account, message);
|
||||
this.replyActions = replyActions;
|
||||
|
||||
return sender != null && sender.length != 0 && !Arrays.equals(from, sender);
|
||||
setDefaultReplyAction(replyActions.getDefaultAction());
|
||||
}
|
||||
|
||||
private void setDefaultReplyAction(ReplyAction defaultAction) {
|
||||
if (defaultAction == null) {
|
||||
menuPrimaryActionView.setVisibility(View.GONE);
|
||||
} else {
|
||||
int replyIconResource = getReplyImageResource(defaultAction);
|
||||
menuPrimaryActionView.setImageResource(replyIconResource);
|
||||
|
||||
String replyActionName = getReplyActionName(defaultAction);
|
||||
TooltipCompat.setTooltipText(menuPrimaryActionView, replyActionName);
|
||||
|
||||
menuPrimaryActionView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
private int getReplyImageResource(@NonNull ReplyAction replyAction) {
|
||||
switch (replyAction) {
|
||||
case REPLY: {
|
||||
return R.drawable.ic_reply;
|
||||
}
|
||||
case REPLY_ALL: {
|
||||
return R.drawable.ic_reply_all;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException("Unknown reply action: " + replyAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getReplyActionName(@NonNull ReplyAction replyAction) {
|
||||
Context context = getContext();
|
||||
switch (replyAction) {
|
||||
case REPLY: {
|
||||
return context.getString(R.string.reply_action);
|
||||
}
|
||||
case REPLY_ALL: {
|
||||
return context.getString(R.string.reply_all_action);
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException("Unknown reply action: " + replyAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setAdditionalReplyActions(PopupMenu popupMenu) {
|
||||
List<ReplyAction> additionalActions = replyActions.getAdditionalActions();
|
||||
if (!additionalActions.contains(ReplyAction.REPLY)) {
|
||||
popupMenu.getMenu().removeItem(R.id.reply);
|
||||
}
|
||||
if (!additionalActions.contains(ReplyAction.REPLY_ALL)) {
|
||||
popupMenu.getMenu().removeItem(R.id.reply_all);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSubject(@NonNull String subject) {
|
||||
subjectView.setText(subject);
|
||||
}
|
||||
|
||||
public void hideCryptoStatus() {
|
||||
mCryptoStatusIcon.setVisibility(View.GONE);
|
||||
cryptoStatusIcon.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void setCryptoStatusLoading() {
|
||||
|
@ -319,38 +345,13 @@ public class MessageHeader extends LinearLayout implements OnClickListener, OnLo
|
|||
|
||||
private void setCryptoDisplayStatus(MessageCryptoDisplayStatus displayStatus) {
|
||||
int color = ThemeUtils.getStyledColor(getContext(), displayStatus.getColorAttr());
|
||||
mCryptoStatusIcon.setEnabled(displayStatus.isEnabled());
|
||||
mCryptoStatusIcon.setVisibility(View.VISIBLE);
|
||||
mCryptoStatusIcon.setImageResource(displayStatus.getStatusIconRes());
|
||||
mCryptoStatusIcon.setColorFilter(color);
|
||||
cryptoStatusIcon.setEnabled(displayStatus.isEnabled());
|
||||
cryptoStatusIcon.setVisibility(View.VISIBLE);
|
||||
cryptoStatusIcon.setImageResource(displayStatus.getStatusIconRes());
|
||||
cryptoStatusIcon.setColorFilter(color);
|
||||
}
|
||||
|
||||
private void updateAddressField(TextView v, CharSequence text, View label) {
|
||||
boolean hasText = !TextUtils.isEmpty(text);
|
||||
|
||||
v.setText(text);
|
||||
v.setVisibility(hasText ? View.VISIBLE : View.GONE);
|
||||
label.setVisibility(hasText ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand or collapse a TextView by removing or adding the 2 lines limitation
|
||||
*/
|
||||
private void expand(TextView v, boolean expand) {
|
||||
if (expand) {
|
||||
v.setMaxLines(Integer.MAX_VALUE);
|
||||
v.setEllipsize(null);
|
||||
} else {
|
||||
v.setMaxLines(2);
|
||||
v.setEllipsize(android.text.TextUtils.TruncateAt.END);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnCryptoClickListener(OnCryptoClickListener onCryptoClickListener) {
|
||||
this.onCryptoClickListener = onCryptoClickListener;
|
||||
}
|
||||
|
||||
public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
|
||||
this.onMenuItemClickListener = onMenuItemClickListener;
|
||||
public void setMessageHeaderClickListener(MessageHeaderClickListener messageHeaderClickListener) {
|
||||
this.messageHeaderClickListener = messageHeaderClickListener;
|
||||
}
|
||||
}
|
||||
|
|
5
app/ui/legacy/src/main/res/drawable/btn_select_star.xml
Normal file
5
app/ui/legacy/src/main/res/drawable/btn_select_star.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true" android:drawable="@drawable/ic_star" />
|
||||
<item android:state_selected="false" android:drawable="@drawable/ic_star_border" />
|
||||
</selector>
|
10
app/ui/legacy/src/main/res/drawable/dots_vertical.xml
Normal file
10
app/ui/legacy/src/main/res/drawable/dots_vertical.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
|
||||
</vector>
|
|
@ -6,5 +6,5 @@
|
|||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM6,10L6,7L4,7v3L1,10v2h3v3h2v-3h3v-2L6,10zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z" />
|
||||
android:pathData="M13,8c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8s1.79,4 4,4S13,10.21 13,8zM15,10v2h3v3h2v-3h3v-2h-3V7h-2v3H15zM1,18v2h16v-2c0,-2.66 -5.33,-4 -8,-4S1,15.34 1,18z" />
|
||||
</vector>
|
10
app/ui/legacy/src/main/res/drawable/ic_reply.xml
Normal file
10
app/ui/legacy/src/main/res/drawable/ic_reply.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
|
||||
</vector>
|
|
@ -2,7 +2,7 @@
|
|||
<com.fsck.k9.ui.messageview.MessageTopView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/message_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -25,19 +25,30 @@
|
|||
<!-- Header area -->
|
||||
<include layout="@layout/message_view_header"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/show_pictures"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
<FrameLayout
|
||||
android:id="@+id/extra_header_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="4dip"
|
||||
android:layout_marginLeft="6dip"
|
||||
android:layout_marginRight="6dip"
|
||||
android:layout_marginTop="4dip"
|
||||
android:text="@string/message_view_show_pictures_action"
|
||||
android:background="?attr/extraMessageHeaderBackground"
|
||||
android:paddingVertical="4dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
tools:visibility="visible">
|
||||
|
||||
<Button
|
||||
android:id="@+id/show_pictures"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="@string/message_view_show_remote_images_action"
|
||||
app:icon="@drawable/ic_attachment_image" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<Space
|
||||
android:id="@+id/line_of_death"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp" />
|
||||
|
||||
<com.fsck.k9.view.ToolableViewAnimator
|
||||
android:id="@+id/message_layout_animator"
|
||||
|
@ -45,7 +56,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:inAnimation="@anim/fade_in"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
custom:previewInitialChild="1">
|
||||
app:previewInitialChild="1">
|
||||
|
||||
<View
|
||||
android:layout_width="wrap_content"
|
||||
|
|
36
app/ui/legacy/src/main/res/layout/message_bottom_sheet.xml
Normal file
36
app/ui/legacy/src/main/res/layout/message_bottom_sheet.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout 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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/message_details_progress"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginVertical="32dp"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message_details_error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="32dp"
|
||||
android:text="@string/message_details_loading_error"
|
||||
android:visibility="gone"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/message_details_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:overScrollMode="never"
|
||||
android:visibility="gone"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/keyline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="72dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/crypto_status_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/keyline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:srcCompat="@drawable/status_lock_dots_3"
|
||||
tools:tint="?attr/openpgp_green" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/crypto_status_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
app:layout_constraintBottom_toTopOf="@+id/crypto_status_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/keyline"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@string/crypto_msg_title_encrypted_signed_e2e" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/crypto_status_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/keyline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/crypto_status_title"
|
||||
tools:text="@string/crypto_msg_encrypted_sign_verified" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/date"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:focusable="false"
|
||||
android:gravity="end"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
tools:text="Wednesday, 11 January 2023, 10:31 pm" />
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:background="?attr/messageDetailsDividerColor"
|
||||
android:focusable="false" />
|
|
@ -0,0 +1,104 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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/participants_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/contact_picture"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:src="@drawable/ic_contact_picture"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/email"
|
||||
app:layout_constraintEnd_toStartOf="@+id/menu_add_contact"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/contact_picture"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_goneMarginBottom="12dp"
|
||||
tools:text="Alice" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/email"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/menu_add_contact"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="@+id/name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/name"
|
||||
tools:text="alice@domain.example" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_add_contact"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="72dp"
|
||||
android:background="?attr/controlBackground"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/action_add_to_contacts"
|
||||
android:focusable="true"
|
||||
android:paddingHorizontal="12dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/menu_compose"
|
||||
app:layout_constraintTop_toTopOf="@+id/menu_compose"
|
||||
app:srcCompat="?attr/messageDetailsAddContactIcon" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_compose"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="72dp"
|
||||
android:background="?attr/controlBackground"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/compose_action"
|
||||
android:focusable="true"
|
||||
android:paddingHorizontal="12dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/menu_overflow"
|
||||
app:layout_constraintTop_toTopOf="@+id/menu_overflow"
|
||||
app:srcCompat="@drawable/ic_envelope" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_overflow"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="72dp"
|
||||
android:background="?attr/controlBackground"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/abc_action_menu_overflow_description"
|
||||
android:focusable="true"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:srcCompat="@drawable/dots_vertical" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:focusable="false"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textAppearance="?attr/textAppearanceOverline"
|
||||
tools:text="To" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/extra"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textAppearance="?attr/textAppearanceOverline"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="3" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,297 +1,185 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.fsck.k9.view.MessageHeader
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.fsck.k9.view.MessageHeader 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/header_container"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Color chip -->
|
||||
<View
|
||||
<Space
|
||||
android:id="@+id/margin_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chip"
|
||||
android:layout_width="8dip"
|
||||
android:layout_height="match_parent"
|
||||
tools:background="#FF1976D2" />
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:clickable="false"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/subject"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:chipBackgroundColor="#1976D2"
|
||||
tools:text="Account name" />
|
||||
|
||||
<LinearLayout
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/top_margin_barrier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="chip,margin_top" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
>
|
||||
<com.fsck.k9.ui.helper.BottomBaselineTextView
|
||||
android:id="@+id/subject"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/chip"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/flagged"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/top_margin_barrier"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
tools:text="Message subject" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subject"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAppearance="@style/TextAppearance.K9.MediumSmall"
|
||||
tools:text="(no subject)"
|
||||
/>
|
||||
<ImageView
|
||||
android:id="@+id/flagged"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/controlBackground"
|
||||
android:baseline="33dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:src="@drawable/btn_select_star"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/subject"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/crypto_status_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:padding="6dp"
|
||||
app:srcCompat="@drawable/status_lock_disabled"
|
||||
app:tint="?attr/openpgp_grey"
|
||||
android:background="?selectableItemBackground"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/flagged"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:focusable="false"
|
||||
android:checked="false"
|
||||
style="@style/MessageStarStyle"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<com.fsck.k9.ui.ContactBadge
|
||||
android:id="@+id/contact_badge"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
tools:src="@drawable/ic_contact_picture"/>
|
||||
|
||||
<!-- State icons -->
|
||||
<LinearLayout
|
||||
android:id="@+id/icon_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dip"
|
||||
android:layout_marginBottom="2dip"
|
||||
android:layout_below="@+id/contact_badge"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<View
|
||||
android:id="@+id/answered"
|
||||
android:layout_width="32sp"
|
||||
android:layout_height="32sp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="2dp"
|
||||
android:background="?attr/messageListAnswered" />
|
||||
|
||||
<View
|
||||
android:id="@+id/forwarded"
|
||||
android:layout_width="22sp"
|
||||
android:layout_height="22sp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:background="?attr/messageListForwarded" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dip"
|
||||
android:layout_marginStart="2dp" >
|
||||
|
||||
<!-- From -->
|
||||
<TextView
|
||||
android:id="@+id/from"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toStartOf="@+id/status_icon_strip"
|
||||
android:layout_alignBottom="@+id/status_icon_strip"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAppearance="@style/TextAppearance.K9.MediumSmall"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/general_no_sender"
|
||||
android:gravity="center_vertical"
|
||||
/>
|
||||
|
||||
<!-- Sender -->
|
||||
<TextView
|
||||
android:id="@+id/sender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toStartOf="@+id/status_icon_strip"
|
||||
android:paddingTop="0dp"
|
||||
android:layout_below="@+id/from"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAppearance="@style/TextAppearance.K9.Small"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
android:gravity="center_vertical"
|
||||
/>
|
||||
|
||||
<!-- To -->
|
||||
<TextView
|
||||
android:id="@+id/to_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignStart="@+id/from"
|
||||
android:layout_alignBaseline="@+id/to"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:text="@string/message_to_label"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAppearance="@style/TextAppearance.K9.MediumSmall"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/to"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/to_label"
|
||||
android:layout_below="@+id/sender"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:paddingTop="2dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textAppearance="@style/TextAppearance.K9.MediumSmall" />
|
||||
|
||||
<!-- CC -->
|
||||
<TextView
|
||||
android:id="@+id/cc_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/to_label"
|
||||
android:layout_alignStart="@+id/to_label"
|
||||
android:layout_alignBaseline="@+id/cc"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:text="@string/message_view_cc_label"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:textAppearance="@style/TextAppearance.K9.MediumSmall" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/cc_label"
|
||||
android:layout_below="@+id/to"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:paddingTop="2dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textAppearance="@style/TextAppearance.K9.MediumSmall" />
|
||||
|
||||
<!-- BCC -->
|
||||
<TextView
|
||||
android:id="@+id/bcc_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/cc_label"
|
||||
android:layout_alignStart="@+id/cc_label"
|
||||
android:layout_alignBaseline="@+id/bcc"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:text="@string/message_view_bcc_label"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:textAppearance="@style/TextAppearance.K9.MediumSmall" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bcc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/bcc_label"
|
||||
android:layout_below="@+id/cc"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:paddingTop="2dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textAppearance="@style/TextAppearance.K9.MediumSmall" />
|
||||
|
||||
<!-- Date -->
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/bcc"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:paddingTop="8dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="none"
|
||||
android:textAppearance="@style/TextAppearance.K9.Small"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:id="@+id/status_icon_strip"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_single_message_options"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="?iconActionSingleMessageOptions"
|
||||
android:padding="8dp"
|
||||
android:background="?selectableItemBackground"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_height="1dip"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/participants_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginBottom="4dip"
|
||||
android:background="@drawable/divider_horizontal_email" />
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/messageHeaderBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/contact_picture"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_contact_picture" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/from"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/date"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/contact_picture"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Sender name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:ellipsize="none"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/from"
|
||||
app:layout_constraintEnd_toStartOf="@+id/menu_primary_action"
|
||||
app:layout_constraintStart_toEndOf="@id/from"
|
||||
tools:text="Sep 19" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/crypto_status_icon"
|
||||
android:layout_width="18sp"
|
||||
android:layout_height="18sp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/recipients"
|
||||
app:layout_constraintStart_toEndOf="@id/contact_picture"
|
||||
app:srcCompat="@drawable/status_lock_disabled"
|
||||
app:tint="?attr/openpgp_grey"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.fsck.k9.ui.messageview.RecipientNamesView
|
||||
android:id="@+id/recipients"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/menu_primary_action"
|
||||
app:layout_constraintStart_toEndOf="@id/crypto_status_icon"
|
||||
app:layout_constraintTop_toBottomOf="@+id/from"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_goneMarginStart="16dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_primary_action"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/controlBackground"
|
||||
android:paddingHorizontal="12dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/menu_overflow"
|
||||
app:layout_constraintTop_toTopOf="@+id/menu_overflow"
|
||||
app:srcCompat="?iconActionSingleMessageOptions" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_overflow"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="?attr/controlBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/dots_vertical" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.fsck.k9.view.MessageHeader>
|
||||
|
|
27
app/ui/legacy/src/main/res/layout/recipient_names.xml
Normal file
27
app/ui/legacy/src/main/res/layout/recipient_names.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="LinearLayout"
|
||||
tools:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recipient_names"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="to me" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recipient_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
android:textColor="?attr/colorAccent"
|
||||
tools:text="+2" />
|
||||
|
||||
</merge>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/copy_email_address"
|
||||
android:title="@string/action_copy_email_address" />
|
||||
<item
|
||||
android:id="@+id/copy_name_and_email_address"
|
||||
android:title="@string/action_copy_name_and_email_address" />
|
||||
</menu>
|
|
@ -208,7 +208,6 @@
|
|||
<string name="message_view_cc_label">نسخة كربونية :</string>
|
||||
<string name="message_view_bcc_label">نسخة مخفية:</string>
|
||||
<string name="message_view_status_attachment_not_saved">غير قادر على حفظ المُرفَق.</string>
|
||||
<string name="message_view_show_pictures_action">اظهر الصور</string>
|
||||
<string name="message_view_no_viewer">يتعذر إيجاب تطبيق لعرض <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">تنزيل الرسالة كاملةً</string>
|
||||
<string name="message_view_sender_label">عبر %1$s</string>
|
||||
|
@ -599,9 +598,6 @@
|
|||
<string name="font_size_message_list_preview">معاينة</string>
|
||||
<string name="font_size_message_view">رسائل</string>
|
||||
<string name="font_size_message_view_sender">مُرسِل</string>
|
||||
<string name="font_size_message_view_to">إلى</string>
|
||||
<string name="font_size_message_view_cc">نسخة</string>
|
||||
<string name="font_size_message_view_additional_headers">ترويسات إضافية</string>
|
||||
<string name="font_size_message_view_subject">العنوان</string>
|
||||
<string name="font_size_message_view_date">الوقت والتاريخ</string>
|
||||
<string name="font_size_message_view_content">نصّ الرسالة</string>
|
||||
|
|
|
@ -208,7 +208,6 @@ K-9 Mail - шматфункцыянальны свабодны паштовы к
|
|||
<string name="message_view_cc_label">Копія:</string>
|
||||
<string name="message_view_bcc_label">Схаваная копія:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Немагчыма захаваць далучаныя файлы.</string>
|
||||
<string name="message_view_show_pictures_action">Прагляд малюнкаў</string>
|
||||
<string name="message_view_no_viewer">Няма праграмы для прагляду <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Спампаваць ліст цалкам</string>
|
||||
<string name="message_view_sender_label">ад %1$s</string>
|
||||
|
@ -662,10 +661,6 @@ K-9 Mail - шматфункцыянальны свабодны паштовы к
|
|||
<string name="font_size_message_list_preview">Папярэдні прагляд</string>
|
||||
<string name="font_size_message_view">Паведамленне</string>
|
||||
<string name="font_size_message_view_sender">Адпраўнік</string>
|
||||
<string name="font_size_message_view_to">Каму</string>
|
||||
<string name="font_size_message_view_cc">Копія</string>
|
||||
<string name="font_size_message_view_bcc">Схаваная копія</string>
|
||||
<string name="font_size_message_view_additional_headers">Дадатковыя загалоўкі</string>
|
||||
<string name="font_size_message_view_subject">Тэма</string>
|
||||
<string name="font_size_message_view_date">Час і дата</string>
|
||||
<string name="font_size_message_view_content">Цела ліста</string>
|
||||
|
|
|
@ -214,7 +214,6 @@ K-9 Mail е мощен, безплатен имейл клиент за Андр
|
|||
<string name="message_view_cc_label">Копие:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Не може да запази прикачения файл.</string>
|
||||
<string name="message_view_show_pictures_action">Покажи картинките</string>
|
||||
<string name="message_view_no_viewer">Не може да намери програма за <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Свали цялото съобщение</string>
|
||||
<string name="message_view_sender_label">през %1$s</string>
|
||||
|
@ -659,10 +658,6 @@ K-9 Mail е мощен, безплатен имейл клиент за Андр
|
|||
<string name="font_size_message_list_preview">Преглед</string>
|
||||
<string name="font_size_message_view">Съобщения</string>
|
||||
<string name="font_size_message_view_sender">Изпращач</string>
|
||||
<string name="font_size_message_view_to">До</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Допълнителни header-и</string>
|
||||
<string name="font_size_message_view_subject">Тема</string>
|
||||
<string name="font_size_message_view_date">Дата и час</string>
|
||||
<string name="font_size_message_view_content">Тяло на съобщението</string>
|
||||
|
|
|
@ -195,7 +195,6 @@ Danevellit beugoù, kenlabourit war keweriusterioù nevez ha savit goulennoù wa
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">N\'haller ket enrollañ ar stagadenn.</string>
|
||||
<string name="message_view_show_pictures_action">Diskouez ar skeudennoù</string>
|
||||
<string name="message_view_no_viewer">N’haller ket kavout ur gwelerez evit <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Pellgargañ ar gemennadenn glok</string>
|
||||
<string name="message_view_sender_label">dre %1$s</string>
|
||||
|
@ -630,10 +629,6 @@ Danevellit beugoù, kenlabourit war keweriusterioù nevez ha savit goulennoù wa
|
|||
<string name="font_size_message_list_preview">Alberz</string>
|
||||
<string name="font_size_message_view">Kemennadennoù</string>
|
||||
<string name="font_size_message_view_sender">Kaser</string>
|
||||
<string name="font_size_message_view_to">Da</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Talbennoù ouzhpenn</string>
|
||||
<string name="font_size_message_view_subject">Danvez</string>
|
||||
<string name="font_size_message_view_date">Eur ha deiziad</string>
|
||||
<string name="font_size_message_view_content">Korf ar gemennadenn</string>
|
||||
|
|
|
@ -224,7 +224,6 @@ Si us plau, envieu informes d\'errors, contribuïu-hi amb noves millores i feu p
|
|||
<string name="message_view_cc_label">A/c:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">No s\'ha pogut desar l\'adjunt.</string>
|
||||
<string name="message_view_show_pictures_action">Mostra les imatges</string>
|
||||
<string name="message_view_no_viewer">No s\'ha pogut trobar un visualitzador per <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Baixa el missatge complet</string>
|
||||
<string name="message_view_sender_label">mitjançant %1$s</string>
|
||||
|
@ -724,10 +723,6 @@ Si us plau, envieu informes d\'errors, contribuïu-hi amb noves millores i feu p
|
|||
<string name="font_size_message_list_preview">Vista prèvia</string>
|
||||
<string name="font_size_message_view">Missatges</string>
|
||||
<string name="font_size_message_view_sender">Remitent</string>
|
||||
<string name="font_size_message_view_to">A</string>
|
||||
<string name="font_size_message_view_cc">A/c</string>
|
||||
<string name="font_size_message_view_bcc">C/o</string>
|
||||
<string name="font_size_message_view_additional_headers">Capçaleres addicionals</string>
|
||||
<string name="font_size_message_view_subject">Assumpte</string>
|
||||
<string name="font_size_message_view_date">Data i hora</string>
|
||||
<string name="font_size_message_view_content">Cos del missatge</string>
|
||||
|
|
|
@ -228,7 +228,6 @@ Hlášení o chyb, úpravy pro nové funkce a dotazy zadávejte prostřednictví
|
|||
<string name="message_view_cc_label">Kopie:</string>
|
||||
<string name="message_view_bcc_label">Skrytá kopie (Bcc):</string>
|
||||
<string name="message_view_status_attachment_not_saved">Přílohu se nedaří uložit.</string>
|
||||
<string name="message_view_show_pictures_action">Zobrazovat obrázky</string>
|
||||
<string name="message_view_no_viewer">Nelze nalézt prohlížeč pro <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Stáhnout úplnou zprávu</string>
|
||||
<string name="message_view_sender_label">přes %1$s</string>
|
||||
|
@ -721,10 +720,6 @@ Hlášení o chyb, úpravy pro nové funkce a dotazy zadávejte prostřednictví
|
|||
<string name="font_size_message_list_preview">Náhled</string>
|
||||
<string name="font_size_message_view">Zpráva</string>
|
||||
<string name="font_size_message_view_sender">Odesílatel</string>
|
||||
<string name="font_size_message_view_to">Příjemce (Komu)</string>
|
||||
<string name="font_size_message_view_cc">Příjemce (Kopie)</string>
|
||||
<string name="font_size_message_view_bcc">Skrytá kopie</string>
|
||||
<string name="font_size_message_view_additional_headers">Další záhlaví</string>
|
||||
<string name="font_size_message_view_subject">Předmět</string>
|
||||
<string name="font_size_message_view_date">Datum a čas</string>
|
||||
<string name="font_size_message_view_content">Tělo</string>
|
||||
|
|
|
@ -229,7 +229,6 @@ Plîs rho wybod am unrhyw wallau, syniadau am nodweddion newydd, neu ofyn cwesti
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Methu â chadw\'r atodiad.</string>
|
||||
<string name="message_view_show_pictures_action">Dangos delweddau</string>
|
||||
<string name="message_view_no_viewer">Methwyd cael hyd i ap er mwyn darllen <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Lawrlwytho\'r neges gyfan</string>
|
||||
<string name="message_view_sender_label">trwy %1$s</string>
|
||||
|
@ -723,10 +722,6 @@ Plîs rho wybod am unrhyw wallau, syniadau am nodweddion newydd, neu ofyn cwesti
|
|||
<string name="font_size_message_list_preview">Rhagolwg</string>
|
||||
<string name="font_size_message_view">Negeseuon</string>
|
||||
<string name="font_size_message_view_sender">Anfonwr</string>
|
||||
<string name="font_size_message_view_to">At</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Penynnau ychwanegol</string>
|
||||
<string name="font_size_message_view_subject">Pwnc</string>
|
||||
<string name="font_size_message_view_date">Amser a dyddiad</string>
|
||||
<string name="font_size_message_view_content">Corff neges</string>
|
||||
|
|
|
@ -218,7 +218,6 @@ Rapporter venligst fejl, forslag til nye funktioner eller stil spørgsmål på:
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Kunne ikke gemme vedhæftning.</string>
|
||||
<string name="message_view_show_pictures_action">Vis billeder</string>
|
||||
<string name="message_view_no_viewer">Kan ikke finde program som kan vise <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Hent hele meddelelsen</string>
|
||||
<string name="message_view_sender_label">via %1$s</string>
|
||||
|
@ -688,10 +687,6 @@ Rapporter venligst fejl, forslag til nye funktioner eller stil spørgsmål på:
|
|||
<string name="font_size_message_list_preview">Forhåndsvisning</string>
|
||||
<string name="font_size_message_view">Meddelelse</string>
|
||||
<string name="font_size_message_view_sender">Afsender</string>
|
||||
<string name="font_size_message_view_to">Modtagere (Til)</string>
|
||||
<string name="font_size_message_view_cc">Modtagere (Cc)</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Yderligere headere</string>
|
||||
<string name="font_size_message_view_subject">Emne</string>
|
||||
<string name="font_size_message_view_date">Tid og dato</string>
|
||||
<string name="font_size_message_view_content">Indhold</string>
|
||||
|
|
|
@ -223,7 +223,6 @@ Bitte senden Sie Fehlerberichte, Ideen für neue Funktionen und stellen Sie Frag
|
|||
<string name="message_view_cc_label">CC:</string>
|
||||
<string name="message_view_bcc_label">BCC:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Anhang konnte nicht gespeichert werden</string>
|
||||
<string name="message_view_show_pictures_action">Bilder herunterladen</string>
|
||||
<string name="message_view_no_viewer">Es wurde kein Anzeigeprogramm für <xliff:g id="mimetype">%s</xliff:g> gefunden.</string>
|
||||
<string name="message_view_download_remainder">Gesamte Nachricht herunterladen</string>
|
||||
<string name="message_view_sender_label">über %1$s</string>
|
||||
|
@ -724,10 +723,6 @@ Bitte senden Sie Fehlerberichte, Ideen für neue Funktionen und stellen Sie Frag
|
|||
<string name="font_size_message_list_preview">Vorschau</string>
|
||||
<string name="font_size_message_view">Nachrichtenanzeige</string>
|
||||
<string name="font_size_message_view_sender">Absender</string>
|
||||
<string name="font_size_message_view_to">Empfänger (To)</string>
|
||||
<string name="font_size_message_view_cc">Empfänger (CC)</string>
|
||||
<string name="font_size_message_view_bcc">Blindkopie (BCC)</string>
|
||||
<string name="font_size_message_view_additional_headers">Zusätzliche Header</string>
|
||||
<string name="font_size_message_view_subject">Betreff</string>
|
||||
<string name="font_size_message_view_date">Uhrzeit und Datum</string>
|
||||
<string name="font_size_message_view_content">Nachrichtentext</string>
|
||||
|
|
|
@ -225,7 +225,6 @@
|
|||
<string name="message_view_cc_label">Κοιν:</string>
|
||||
<string name="message_view_bcc_label">Κρυφή κοιν:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Αδυναμία αποθήκευσης συνημμένου.</string>
|
||||
<string name="message_view_show_pictures_action">Εμφάνιση εικόνων</string>
|
||||
<string name="message_view_no_viewer">Δεν υπάρχει πρόγραμμα προβολής για <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Λήψη πλήρους μηνύματος</string>
|
||||
<string name="message_view_sender_label">μέσω %1$s</string>
|
||||
|
@ -719,10 +718,6 @@
|
|||
<string name="font_size_message_list_preview">Προεπισκόπηση</string>
|
||||
<string name="font_size_message_view">Μηνύματα</string>
|
||||
<string name="font_size_message_view_sender">Αποστολέας</string>
|
||||
<string name="font_size_message_view_to">Προς</string>
|
||||
<string name="font_size_message_view_cc">Κοινοποίηση</string>
|
||||
<string name="font_size_message_view_bcc">Κρυφή Κοιν:</string>
|
||||
<string name="font_size_message_view_additional_headers">Επιπλέον επικεφαλίδες</string>
|
||||
<string name="font_size_message_view_subject">Θέμα</string>
|
||||
<string name="font_size_message_view_date">Ώρα και Ημερομηνία</string>
|
||||
<string name="font_size_message_view_content">Σώμα μηνύματος</string>
|
||||
|
|
|
@ -212,7 +212,6 @@ Bonvolu raporti erarojn, kontribui novajn eblojn kaj peti pri novaj funkcioj per
|
|||
<string name="message_view_cc_label">Kopio:</string>
|
||||
<string name="message_view_bcc_label">Kaŝkopio:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Ne povis konservi kunsendaĵon.</string>
|
||||
<string name="message_view_show_pictures_action">Montri bildojn</string>
|
||||
<string name="message_view_no_viewer">Ne povas trovi vidigilon por <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Elŝuti tutan mesaĝon</string>
|
||||
<string name="message_view_sender_label">per %1$s</string>
|
||||
|
@ -677,10 +676,6 @@ Bonvolu raporti erarojn, kontribui novajn eblojn kaj peti pri novaj funkcioj per
|
|||
<string name="font_size_message_list_preview">Antaŭmontro</string>
|
||||
<string name="font_size_message_view">Mesaĝoj</string>
|
||||
<string name="font_size_message_view_sender">Sendinto</string>
|
||||
<string name="font_size_message_view_to">Ricevonto</string>
|
||||
<string name="font_size_message_view_cc">Ricevonto de kopio</string>
|
||||
<string name="font_size_message_view_bcc">Kaŝkopio</string>
|
||||
<string name="font_size_message_view_additional_headers">Aldonaj kapoj</string>
|
||||
<string name="font_size_message_view_subject">Temo</string>
|
||||
<string name="font_size_message_view_date">Tempo kaj dato</string>
|
||||
<string name="font_size_message_view_content">Mesaĝa enhavo</string>
|
||||
|
|
|
@ -224,7 +224,6 @@ Puedes informar de fallos, contribuir con su desarrollo y hacer preguntas en <a
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Cco:</string>
|
||||
<string name="message_view_status_attachment_not_saved">No se puede guardar el adjunto.</string>
|
||||
<string name="message_view_show_pictures_action">Mostrar imágenes</string>
|
||||
<string name="message_view_no_viewer">Imposible encontrar visor de <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Descargar mensaje completo</string>
|
||||
<string name="message_view_sender_label">vía %1$s</string>
|
||||
|
@ -724,10 +723,6 @@ Puedes informar de fallos, contribuir con su desarrollo y hacer preguntas en <a
|
|||
<string name="font_size_message_list_preview">Previsualizar</string>
|
||||
<string name="font_size_message_view">Mensajes</string>
|
||||
<string name="font_size_message_view_sender">Remitente</string>
|
||||
<string name="font_size_message_view_to">Para</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Cco</string>
|
||||
<string name="font_size_message_view_additional_headers">Encabezados adicionales</string>
|
||||
<string name="font_size_message_view_subject">Asunto</string>
|
||||
<string name="font_size_message_view_date">Hora y fecha</string>
|
||||
<string name="font_size_message_view_content">Cuerpo de mensaje</string>
|
||||
|
|
|
@ -225,7 +225,6 @@ Veateated saad saata, kaastööd teha ning küsida teavet järgmisel lehel:
|
|||
<string name="message_view_cc_label">Koopia:</string>
|
||||
<string name="message_view_bcc_label">Pimekoopia:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Manuse salvestamine ebaõnnestus</string>
|
||||
<string name="message_view_show_pictures_action">Näita pilte</string>
|
||||
<string name="message_view_no_viewer">Ei leia <xliff:g id="mimetype">%s</xliff:g> kuvamiseks programmi.</string>
|
||||
<string name="message_view_download_remainder">Laadi kogu kiri alla</string>
|
||||
<string name="message_view_sender_label">%1$s kaudu</string>
|
||||
|
@ -722,10 +721,6 @@ Veateated saad saata, kaastööd teha ning küsida teavet järgmisel lehel:
|
|||
<string name="font_size_message_list_preview">Eelvaade</string>
|
||||
<string name="font_size_message_view">Kirjad</string>
|
||||
<string name="font_size_message_view_sender">Saatja</string>
|
||||
<string name="font_size_message_view_to">Kellele</string>
|
||||
<string name="font_size_message_view_cc">Koopia</string>
|
||||
<string name="font_size_message_view_bcc">Pimekoopia</string>
|
||||
<string name="font_size_message_view_additional_headers">Täiendavad pealkirjad</string>
|
||||
<string name="font_size_message_view_subject">Teema</string>
|
||||
<string name="font_size_message_view_date">Kellaaeg ja kuupäev</string>
|
||||
<string name="font_size_message_view_content">Kirja põhiosa</string>
|
||||
|
|
|
@ -223,7 +223,6 @@ Mesedez akatsen berri emateko, ezaugarri berriak gehitzeko eta galderak egiteko
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Ezin izan da eranskina gorde.</string>
|
||||
<string name="message_view_show_pictures_action">Erakutsi irudiak</string>
|
||||
<string name="message_view_no_viewer">Ezin da <xliff:g id="mimetype">%s</xliff:g>-(r)entzako ikustailerik aurkitu.</string>
|
||||
<string name="message_view_download_remainder">Deskargatu mezu osoa</string>
|
||||
<string name="message_view_sender_label">%1$s-(r)en bidez</string>
|
||||
|
@ -723,10 +722,6 @@ Mesedez akatsen berri emateko, ezaugarri berriak gehitzeko eta galderak egiteko
|
|||
<string name="font_size_message_list_preview">Aurreikusi</string>
|
||||
<string name="font_size_message_view">Mezuak</string>
|
||||
<string name="font_size_message_view_sender">Bidaltzailea</string>
|
||||
<string name="font_size_message_view_to">Nori</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Goiburu osagarriak</string>
|
||||
<string name="font_size_message_view_subject">Gaia</string>
|
||||
<string name="font_size_message_view_date">Ordua eta data</string>
|
||||
<string name="font_size_message_view_content">Mezuaren gorputza</string>
|
||||
|
|
|
@ -224,7 +224,6 @@
|
|||
<string name="message_view_cc_label">رونوشت به:</string>
|
||||
<string name="message_view_bcc_label">مخفیانه به:</string>
|
||||
<string name="message_view_status_attachment_not_saved">نمیتوان پیوست را ذخیره کرد.</string>
|
||||
<string name="message_view_show_pictures_action">نمایش تصاویر</string>
|
||||
<string name="message_view_no_viewer">نمایشدهندهای برای <xliff:g id="mimetype">%s</xliff:g> پیدا نشد.</string>
|
||||
<string name="message_view_download_remainder">دانلود پیام کامل</string>
|
||||
<string name="message_view_sender_label">از طریق %1$s</string>
|
||||
|
@ -717,10 +716,6 @@
|
|||
<string name="font_size_message_list_preview">پیشنمایش</string>
|
||||
<string name="font_size_message_view">پیامها</string>
|
||||
<string name="font_size_message_view_sender">فرستنده</string>
|
||||
<string name="font_size_message_view_to">به</string>
|
||||
<string name="font_size_message_view_cc">رونوشت به</string>
|
||||
<string name="font_size_message_view_bcc">مخفیانه به</string>
|
||||
<string name="font_size_message_view_additional_headers">سرایندهای اضافی</string>
|
||||
<string name="font_size_message_view_subject">موضوع</string>
|
||||
<string name="font_size_message_view_date">ساعت و تاریخ</string>
|
||||
<string name="font_size_message_view_content">متن پیام</string>
|
||||
|
|
|
@ -223,7 +223,6 @@ Ilmoita virheistä, ota osaa sovelluskehitykseen ja esitä kysymyksiä osoittees
|
|||
<string name="message_view_cc_label">Kopio:</string>
|
||||
<string name="message_view_bcc_label">Piilokopio:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Liitteen tallentaminen epäonnistui.</string>
|
||||
<string name="message_view_show_pictures_action">Näytä kuvat</string>
|
||||
<string name="message_view_no_viewer">Tiedostotyypille <xliff:g id="mimetype">%s</xliff:g> ei löydy katseluohjelmaa.</string>
|
||||
<string name="message_view_download_remainder">Lataa koko viesti</string>
|
||||
<string name="message_view_sender_label">%1$s kautta</string>
|
||||
|
@ -723,10 +722,6 @@ Ilmoita virheistä, ota osaa sovelluskehitykseen ja esitä kysymyksiä osoittees
|
|||
<string name="font_size_message_list_preview">Esikatselu</string>
|
||||
<string name="font_size_message_view">Viestinäkymä</string>
|
||||
<string name="font_size_message_view_sender">Lähettäjä</string>
|
||||
<string name="font_size_message_view_to">Vastaanottaja</string>
|
||||
<string name="font_size_message_view_cc">Kopio</string>
|
||||
<string name="font_size_message_view_bcc">Piilokopio</string>
|
||||
<string name="font_size_message_view_additional_headers">Tarkemmat otsikkotiedot</string>
|
||||
<string name="font_size_message_view_subject">Viestin aihe</string>
|
||||
<string name="font_size_message_view_date">Aika ja päivämäärä</string>
|
||||
<string name="font_size_message_view_content">Viestin sisältö</string>
|
||||
|
|
|
@ -227,7 +227,6 @@ Rapportez les bogues, recommandez de nouvelles fonctions et posez vos questions
|
|||
<string name="message_view_cc_label">Cc :</string>
|
||||
<string name="message_view_bcc_label">Cci :</string>
|
||||
<string name="message_view_status_attachment_not_saved">Impossible d’enregistrer le fichier joint.</string>
|
||||
<string name="message_view_show_pictures_action">Afficher les images</string>
|
||||
<string name="message_view_no_viewer">Impossible de trouver un visualiseur pour <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Télécharger le courriel complet</string>
|
||||
<string name="message_view_sender_label">par %1$s</string>
|
||||
|
@ -727,10 +726,6 @@ Rapportez les bogues, recommandez de nouvelles fonctions et posez vos questions
|
|||
<string name="font_size_message_list_preview">Aperçu</string>
|
||||
<string name="font_size_message_view">Courriels</string>
|
||||
<string name="font_size_message_view_sender">Expéditeur</string>
|
||||
<string name="font_size_message_view_to">À</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Cci</string>
|
||||
<string name="font_size_message_view_additional_headers">En-têtes supplémentaires</string>
|
||||
<string name="font_size_message_view_subject">Objet</string>
|
||||
<string name="font_size_message_view_date">Horodatage</string>
|
||||
<string name="font_size_message_view_content">Corps du courriel</string>
|
||||
|
|
|
@ -219,7 +219,6 @@ Graach flaterrapporten stjoere, bydragen foar nije funksjes en fragen stelle op
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Kin bylage net bewarje.</string>
|
||||
<string name="message_view_show_pictures_action">Ofbyldingen toane</string>
|
||||
<string name="message_view_no_viewer">Gjin viewer te finen foar <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Folslein berjocht downloade</string>
|
||||
<string name="message_view_sender_label">fia %1$s</string>
|
||||
|
@ -719,10 +718,6 @@ Graach flaterrapporten stjoere, bydragen foar nije funksjes en fragen stelle op
|
|||
<string name="font_size_message_list_preview">Foarbyld</string>
|
||||
<string name="font_size_message_view">Berjochten</string>
|
||||
<string name="font_size_message_view_sender">Ofstjoerder</string>
|
||||
<string name="font_size_message_view_to">Oan</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Ekstra koppen</string>
|
||||
<string name="font_size_message_view_subject">Underwerp</string>
|
||||
<string name="font_size_message_view_date">Tiid en datum</string>
|
||||
<string name="font_size_message_view_content">Berichtynhâld</string>
|
||||
|
|
|
@ -180,7 +180,6 @@
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Cha b’ urrainn dhuinn an ceanglachan a shàbhaladh.</string>
|
||||
<string name="message_view_show_pictures_action">Seall na dealbhan</string>
|
||||
<string name="message_view_no_viewer">Chan urrainn dhuinn aplacaid a lorg a sheallas <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Luchdaich a-nuas an teachdaireachd shlàn</string>
|
||||
<string name="message_view_sender_label">slighe %1$s</string>
|
||||
|
@ -625,10 +624,6 @@
|
|||
<string name="font_size_message_list_preview">Ro-shealladh</string>
|
||||
<string name="font_size_message_view">Teachdaireachdan</string>
|
||||
<string name="font_size_message_view_sender">Seòladair</string>
|
||||
<string name="font_size_message_view_to">Gu</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Bannan-cinn a bharrachd</string>
|
||||
<string name="font_size_message_view_subject">Cuspair</string>
|
||||
<string name="font_size_message_view_date">Ceann-là ’s àm</string>
|
||||
<string name="font_size_message_view_content">Bodhaig na teachdaireachd</string>
|
||||
|
|
|
@ -129,7 +129,6 @@
|
|||
<string name="message_view_from_format">Desde: <xliff:g id="name">%1$s</xliff:g> <<xliff:g id="email">%2$s</xliff:g>></string>
|
||||
<string name="message_to_label">Para:</string>
|
||||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_show_pictures_action">Mostrar imaxes</string>
|
||||
<string name="message_view_no_viewer">Non é posible atopar un visor para <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Descargar mensaxe completa</string>
|
||||
<string name="from_same_sender">Máis deste remitente</string>
|
||||
|
@ -530,10 +529,6 @@
|
|||
<string name="font_size_message_list_preview">Previsualización</string>
|
||||
<string name="font_size_message_view">Mensaxes</string>
|
||||
<string name="font_size_message_view_sender">Remitente</string>
|
||||
<string name="font_size_message_view_to">Para</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Cabeceiras adicionais</string>
|
||||
<string name="font_size_message_view_subject">Asunto</string>
|
||||
<string name="font_size_message_view_date">Hora e data</string>
|
||||
<string name="font_size_message_view_content">Corpo da mensaxe</string>
|
||||
|
|
|
@ -218,7 +218,6 @@ Por favor envíen informes de fallos, contribúa con novas características e co
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Incapaz de gardar o anexo.</string>
|
||||
<string name="message_view_show_pictures_action">Amosar imaxe</string>
|
||||
<string name="message_view_no_viewer">Imposible atopar visor para <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Descargar mensaxe completa</string>
|
||||
<string name="message_view_sender_label">vía %1$s</string>
|
||||
|
@ -683,10 +682,6 @@ Por favor envíen informes de fallos, contribúa con novas características e co
|
|||
<string name="font_size_message_list_preview">Vista previa</string>
|
||||
<string name="font_size_message_view">mensaxes</string>
|
||||
<string name="font_size_message_view_sender">Remitente</string>
|
||||
<string name="font_size_message_view_to">Para</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">CCo</string>
|
||||
<string name="font_size_message_view_additional_headers">Encabezados adicionáis</string>
|
||||
<string name="font_size_message_view_subject">Asunto</string>
|
||||
<string name="font_size_message_view_date">Data e Hora</string>
|
||||
<string name="font_size_message_view_content">Corpo da mensaxe</string>
|
||||
|
|
|
@ -168,7 +168,6 @@
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Nije moguće spremiti privitak.</string>
|
||||
<string name="message_view_show_pictures_action">Prikaži slike</string>
|
||||
<string name="message_view_no_viewer">Ne mogu pronaći preglednik za <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Preuzmi cijelu poruku</string>
|
||||
<string name="message_view_sender_label">putem %1$s</string>
|
||||
|
@ -621,10 +620,6 @@
|
|||
<string name="font_size_message_list_preview">Pedpregled</string>
|
||||
<string name="font_size_message_view">Poruke</string>
|
||||
<string name="font_size_message_view_sender">Pošiljatelj</string>
|
||||
<string name="font_size_message_view_to">Za</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Dodatna zaglavlja</string>
|
||||
<string name="font_size_message_view_subject">Subjekt</string>
|
||||
<string name="font_size_message_view_date">Vrijeme i datum</string>
|
||||
<string name="font_size_message_view_content">Tijelo poruke</string>
|
||||
|
|
|
@ -222,7 +222,6 @@ Hibajelentések beküldésével közreműködhet az új funkciókban, és kérd
|
|||
<string name="message_view_cc_label">Másolat:</string>
|
||||
<string name="message_view_bcc_label">Titkos másolat:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Nem lehet elmenteni a mellékletet.</string>
|
||||
<string name="message_view_show_pictures_action">Fényképek megjelenítése</string>
|
||||
<string name="message_view_no_viewer">Nem sikerült megjelenítőt találni ehhez: <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Teljes üzenet letöltése</string>
|
||||
<string name="message_view_sender_label">ezen keresztül: %1$s</string>
|
||||
|
@ -715,10 +714,6 @@ Hibajelentések beküldésével közreműködhet az új funkciókban, és kérd
|
|||
<string name="font_size_message_list_preview">Előnézet</string>
|
||||
<string name="font_size_message_view">Üzenetek</string>
|
||||
<string name="font_size_message_view_sender">Feladó</string>
|
||||
<string name="font_size_message_view_to">Címzett</string>
|
||||
<string name="font_size_message_view_cc">Másolat</string>
|
||||
<string name="font_size_message_view_bcc">Titkos másolat</string>
|
||||
<string name="font_size_message_view_additional_headers">További fejlécek</string>
|
||||
<string name="font_size_message_view_subject">Tárgy</string>
|
||||
<string name="font_size_message_view_date">Idő és dátum</string>
|
||||
<string name="font_size_message_view_content">Üzenet törzse</string>
|
||||
|
|
|
@ -203,7 +203,6 @@ Kirimkan laporan bug, kontribusikan fitur baru dan ajukan pertanyaan di
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Tidak dapat menyimpan lampiran.</string>
|
||||
<string name="message_view_show_pictures_action">Tampilkan gambar</string>
|
||||
<string name="message_view_no_viewer">Tidak bisa menemukan penampil untuk <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Unduh seluruh pesan</string>
|
||||
<string name="message_view_sender_label">melalui %1$s</string>
|
||||
|
@ -657,10 +656,6 @@ Kirimkan laporan bug, kontribusikan fitur baru dan ajukan pertanyaan di
|
|||
<string name="font_size_message_list_preview">Pratinjau</string>
|
||||
<string name="font_size_message_view">Pesan</string>
|
||||
<string name="font_size_message_view_sender">Pengirim</string>
|
||||
<string name="font_size_message_view_to">Kepada</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Header tambahan</string>
|
||||
<string name="font_size_message_view_subject">Subyek</string>
|
||||
<string name="font_size_message_view_date">Tanggal dan waktu</string>
|
||||
<string name="font_size_message_view_content">Isi pesan</string>
|
||||
|
|
|
@ -224,7 +224,6 @@ Sendu inn villuskýrslur, leggðu fram nýja eiginleika og spurðu spurninga á
|
|||
<string name="message_view_cc_label">Afrit:</string>
|
||||
<string name="message_view_bcc_label">Falið afrit:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Tókst ekki að vista viðhengi</string>
|
||||
<string name="message_view_show_pictures_action">Sýna myndir</string>
|
||||
<string name="message_view_no_viewer">Gat ekki fundið neitt skoðunarforrit fyrir <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Niðurhala heil skilaboð</string>
|
||||
<string name="message_view_sender_label">í gegnum %1$s</string>
|
||||
|
@ -724,10 +723,6 @@ Sendu inn villuskýrslur, leggðu fram nýja eiginleika og spurðu spurninga á
|
|||
<string name="font_size_message_list_preview">Forskoðun</string>
|
||||
<string name="font_size_message_view">Skilaboð</string>
|
||||
<string name="font_size_message_view_sender">Sendandi</string>
|
||||
<string name="font_size_message_view_to">Til</string>
|
||||
<string name="font_size_message_view_cc">Afrit</string>
|
||||
<string name="font_size_message_view_bcc">Falið afrit</string>
|
||||
<string name="font_size_message_view_additional_headers">Viðbótarhausar</string>
|
||||
<string name="font_size_message_view_subject">Viðfangsefni</string>
|
||||
<string name="font_size_message_view_date">Tími og dagsetning</string>
|
||||
<string name="font_size_message_view_content">Efni skilaboða</string>
|
||||
|
|
|
@ -227,7 +227,6 @@ Invia segnalazioni di bug, contribuisci con nuove funzionalità e poni domande s
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Ccn:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Impossibile salvare l\'allegato.</string>
|
||||
<string name="message_view_show_pictures_action">Mostra immagini</string>
|
||||
<string name="message_view_no_viewer">Impossibile trovare un visualizzatore per <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Scarica messaggio completo</string>
|
||||
<string name="message_view_sender_label">tramite %1$s</string>
|
||||
|
@ -720,10 +719,6 @@ Invia segnalazioni di bug, contribuisci con nuove funzionalità e poni domande s
|
|||
<string name="font_size_message_list_preview">Anteprima</string>
|
||||
<string name="font_size_message_view">Messaggi</string>
|
||||
<string name="font_size_message_view_sender">Mittente</string>
|
||||
<string name="font_size_message_view_to">A</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Ccn</string>
|
||||
<string name="font_size_message_view_additional_headers">Intestazioni aggiuntive</string>
|
||||
<string name="font_size_message_view_subject">Oggetto</string>
|
||||
<string name="font_size_message_view_date">Ora e data</string>
|
||||
<string name="font_size_message_view_content">Corpo del messaggio</string>
|
||||
|
|
|
@ -182,7 +182,6 @@
|
|||
<string name="message_view_from_format">מ: <xliff:g id="name">%1$s</xliff:g> <<xliff:g id="email">%2$s</xliff:g>></string>
|
||||
<string name="message_to_label">ל:</string>
|
||||
<string name="message_view_cc_label">עותק:</string>
|
||||
<string name="message_view_show_pictures_action">הצג תמונות</string>
|
||||
<string name="message_view_no_viewer">לא ניתן למצוא את מציג בשביל <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">הורד את ההודעה המלאה</string>
|
||||
<string name="message_view_sender_label">באמצעות %1$s</string>
|
||||
|
@ -532,10 +531,6 @@
|
|||
<string name="font_size_message_list_preview">תצוגה מקדימה</string>
|
||||
<string name="font_size_message_view">הודעות</string>
|
||||
<string name="font_size_message_view_sender">שולח</string>
|
||||
<string name="font_size_message_view_to">ל</string>
|
||||
<string name="font_size_message_view_cc">עותק</string>
|
||||
<string name="font_size_message_view_bcc">עותק מוסתר</string>
|
||||
<string name="font_size_message_view_additional_headers">כותרות נוספות</string>
|
||||
<string name="font_size_message_view_subject">כותרת</string>
|
||||
<string name="font_size_message_view_date">שעה ותאריך</string>
|
||||
<string name="font_size_message_view_content">גוף ההודעה</string>
|
||||
|
|
|
@ -222,7 +222,6 @@ K-9 は大多数のメールクライアントと同様に、ほとんどのフ
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">添付ファイルを保存できません。</string>
|
||||
<string name="message_view_show_pictures_action">画像表示</string>
|
||||
<string name="message_view_no_viewer"><xliff:g id="mimetype">%s</xliff:g>のビューワーが見つかりません</string>
|
||||
<string name="message_view_download_remainder">すべてダウンロード</string>
|
||||
<string name="message_view_sender_label">%1$s 経由</string>
|
||||
|
@ -722,10 +721,6 @@ K-9 は大多数のメールクライアントと同様に、ほとんどのフ
|
|||
<string name="font_size_message_list_preview">プレビュー</string>
|
||||
<string name="font_size_message_view">メッセージ表示</string>
|
||||
<string name="font_size_message_view_sender">送信者</string>
|
||||
<string name="font_size_message_view_to">To</string>
|
||||
<string name="font_size_message_view_cc">CC</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">追加ヘッダ</string>
|
||||
<string name="font_size_message_view_subject">件名</string>
|
||||
<string name="font_size_message_view_date">日時</string>
|
||||
<string name="font_size_message_view_content">本文</string>
|
||||
|
|
|
@ -162,7 +162,6 @@
|
|||
<string name="message_to_label">수신자:</string>
|
||||
<string name="message_view_cc_label">참조:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_show_pictures_action">그림 보기</string>
|
||||
<string name="message_view_no_viewer"><xliff:g id="mimetype">%s</xliff:g>에 대한 뷰어를 찾을 수 없습니다.</string>
|
||||
<string name="message_view_download_remainder">메시지의 남은 부분 다운로드</string>
|
||||
<string name="message_view_sender_label">%1$s을 통하여</string>
|
||||
|
@ -576,10 +575,6 @@
|
|||
<string name="font_size_message_list_preview">미리 보기</string>
|
||||
<string name="font_size_message_view">메시지</string>
|
||||
<string name="font_size_message_view_sender">보낸이</string>
|
||||
<string name="font_size_message_view_to">받는이</string>
|
||||
<string name="font_size_message_view_cc">참조</string>
|
||||
<string name="font_size_message_view_bcc">숨은 참조</string>
|
||||
<string name="font_size_message_view_additional_headers">추가 헤더 정보</string>
|
||||
<string name="font_size_message_view_subject">제목</string>
|
||||
<string name="font_size_message_view_date">시간 및 날짜</string>
|
||||
<string name="font_size_message_view_content">내용</string>
|
||||
|
|
|
@ -226,7 +226,6 @@ Pateikite pranešimus apie klaidas, prisidėkite prie naujų funkcijų kūrimo i
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Nepavyko išsaugoti priedo.</string>
|
||||
<string name="message_view_show_pictures_action">Rodyti paveikslėlius</string>
|
||||
<string name="message_view_no_viewer">Nepavyko rasto <xliff:g id="mimetype">%s</xliff:g> peržiūros programos.</string>
|
||||
<string name="message_view_download_remainder">Parsiųsti visą laišką</string>
|
||||
<string name="message_view_sender_label">per %1$s</string>
|
||||
|
@ -716,10 +715,6 @@ Pateikite pranešimus apie klaidas, prisidėkite prie naujų funkcijų kūrimo i
|
|||
<string name="font_size_message_list_preview">Peržiūra</string>
|
||||
<string name="font_size_message_view">Laiškai</string>
|
||||
<string name="font_size_message_view_sender">Siuntėjas</string>
|
||||
<string name="font_size_message_view_to">Kam</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Slapta kopija</string>
|
||||
<string name="font_size_message_view_additional_headers">Papildomos antraštės</string>
|
||||
<string name="font_size_message_view_subject">Tema</string>
|
||||
<string name="font_size_message_view_date">Laikas ir data</string>
|
||||
<string name="font_size_message_view_content">Pagrindinė laiško dalis</string>
|
||||
|
|
|
@ -226,7 +226,6 @@ pat <xliff:g id="messages_to_load">%d</xliff:g> vairāk</string>
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Neredzamais:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Nevar saglabāt pielikumu.</string>
|
||||
<string name="message_view_show_pictures_action">Rādīt bildes</string>
|
||||
<string name="message_view_no_viewer">Nevar atrast programmu, lai atvērtu <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">lejupielādēt pilnu vēstuli</string>
|
||||
<string name="message_view_sender_label">caur %1$s</string>
|
||||
|
@ -726,10 +725,6 @@ pat <xliff:g id="messages_to_load">%d</xliff:g> vairāk</string>
|
|||
<string name="font_size_message_list_preview">Priekšskatījums</string>
|
||||
<string name="font_size_message_view">Vēstules</string>
|
||||
<string name="font_size_message_view_sender">Sūtītājs</string>
|
||||
<string name="font_size_message_view_to">Kam</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Neredzamais</string>
|
||||
<string name="font_size_message_view_additional_headers">Izvērsta vēstules papildinformācija</string>
|
||||
<string name="font_size_message_view_subject">Temats</string>
|
||||
<string name="font_size_message_view_date">Laiks un datums</string>
|
||||
<string name="font_size_message_view_content">Vēstules pamatteksts</string>
|
||||
|
|
|
@ -218,7 +218,6 @@
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">അറ്റാച്ചുമെന്റ് സേവ് ചെയ്യാനായില്ല.</string>
|
||||
<string name="message_view_show_pictures_action">ചിത്രങ്ങൾ കാണിക്കുക</string>
|
||||
<string name="message_view_no_viewer"><xliff:g id="mimetype">%s</xliff:g> എന്നതിനുള്ള വീക്ഷണസഹായസാമഗ്രി കണ്ടെത്താനായില്ല.</string>
|
||||
<string name="message_view_download_remainder">പൂർണ്ണ സന്ദേശം ഡൗൺലോഡു ചെയ്യുക</string>
|
||||
<string name="message_view_sender_label">%1$s വഴി</string>
|
||||
|
@ -685,10 +684,6 @@
|
|||
<string name="font_size_message_list_preview">പ്രിവ്യൂ</string>
|
||||
<string name="font_size_message_view">സന്ദേശങ്ങൾ</string>
|
||||
<string name="font_size_message_view_sender">അയച്ചയാൾ</string>
|
||||
<string name="font_size_message_view_to">To</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">അധിക തലക്കെട്ടുകൾ</string>
|
||||
<string name="font_size_message_view_subject">വിഷയം</string>
|
||||
<string name="font_size_message_view_date">സമയവും തീയതിയും</string>
|
||||
<string name="font_size_message_view_content">സന്ദേശ ബോഡി</string>
|
||||
|
|
|
@ -204,7 +204,6 @@ til <xliff:g id="messages_to_load">%d</xliff:g> flere</string>
|
|||
<string name="message_view_cc_label">Kopi:</string>
|
||||
<string name="message_view_bcc_label">Blindkopi:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Klarte ikke lagre vedlegg</string>
|
||||
<string name="message_view_show_pictures_action">Vis bilder</string>
|
||||
<string name="message_view_no_viewer">Finner ikke visningsprogram for <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Last ned komplett melding</string>
|
||||
<string name="message_view_sender_label">via %1$s</string>
|
||||
|
@ -656,10 +655,6 @@ til <xliff:g id="messages_to_load">%d</xliff:g> flere</string>
|
|||
<string name="font_size_message_list_preview">Forhåndsvisning</string>
|
||||
<string name="font_size_message_view">Meldinger</string>
|
||||
<string name="font_size_message_view_sender">Avsender</string>
|
||||
<string name="font_size_message_view_to">Til</string>
|
||||
<string name="font_size_message_view_cc">Kopi</string>
|
||||
<string name="font_size_message_view_bcc">Blindkopi</string>
|
||||
<string name="font_size_message_view_additional_headers">Flere meldingshoder</string>
|
||||
<string name="font_size_message_view_subject">Emne</string>
|
||||
<string name="font_size_message_view_date">Tid og dato</string>
|
||||
<string name="font_size_message_view_content">Meldingstekst</string>
|
||||
|
|
|
@ -219,7 +219,6 @@ Graag foutrapporten sturen, bijdragen voor nieuwe functies en vragen stellen op
|
|||
<string name="message_view_cc_label">CC:</string>
|
||||
<string name="message_view_bcc_label">BCC:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Kan bijlage niet opslaan.</string>
|
||||
<string name="message_view_show_pictures_action">Afbeeldingen tonen</string>
|
||||
<string name="message_view_no_viewer">Niet in staat viewer te vinden voor <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Compleet bericht downloaden</string>
|
||||
<string name="message_view_sender_label">via %1$s</string>
|
||||
|
@ -719,10 +718,6 @@ Graag foutrapporten sturen, bijdragen voor nieuwe functies en vragen stellen op
|
|||
<string name="font_size_message_list_preview">Voorbeeld</string>
|
||||
<string name="font_size_message_view">Berichten</string>
|
||||
<string name="font_size_message_view_sender">Afzender</string>
|
||||
<string name="font_size_message_view_to">Bericht ontvanger (Aan)</string>
|
||||
<string name="font_size_message_view_cc">Bericht ontvanger (CC)</string>
|
||||
<string name="font_size_message_view_bcc">Bericht ontvanger (BCC)</string>
|
||||
<string name="font_size_message_view_additional_headers">Extra koppen</string>
|
||||
<string name="font_size_message_view_subject">Onderwerp</string>
|
||||
<string name="font_size_message_view_date">Tijd en datum</string>
|
||||
<string name="font_size_message_view_content">Berichtinhoud</string>
|
||||
|
|
|
@ -228,7 +228,6 @@ Wysłane z urządzenia Android za pomocą K-9 Mail. Proszę wybaczyć moją zwi
|
|||
<string name="message_view_cc_label">DW:</string>
|
||||
<string name="message_view_bcc_label">UDW:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Nie można zapisać załącznika.</string>
|
||||
<string name="message_view_show_pictures_action">Wyświetl grafikę</string>
|
||||
<string name="message_view_no_viewer">Nie moge znaleźć aplikacji do wyświetlenia pliku <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Pobierz całą wiadomość</string>
|
||||
<string name="message_view_sender_label">przez %1$s</string>
|
||||
|
@ -728,10 +727,6 @@ Wysłane z urządzenia Android za pomocą K-9 Mail. Proszę wybaczyć moją zwi
|
|||
<string name="font_size_message_list_preview">Podgląd</string>
|
||||
<string name="font_size_message_view">Wiadomości</string>
|
||||
<string name="font_size_message_view_sender">Nadawca</string>
|
||||
<string name="font_size_message_view_to">Adresat (Do:)</string>
|
||||
<string name="font_size_message_view_cc">Odbiorca kopii (DW:)</string>
|
||||
<string name="font_size_message_view_bcc">UDW</string>
|
||||
<string name="font_size_message_view_additional_headers">Dodatkowe nagłówki</string>
|
||||
<string name="font_size_message_view_subject">Temat</string>
|
||||
<string name="font_size_message_view_date">Czas i data</string>
|
||||
<string name="font_size_message_view_content">Treść wiadomości</string>
|
||||
|
|
|
@ -225,7 +225,6 @@ Por favor encaminhe relatórios de bugs, contribua com novos recursos e tire dú
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Cco:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Não foi possível salvar o anexo.</string>
|
||||
<string name="message_view_show_pictures_action">Exibir imagens</string>
|
||||
<string name="message_view_no_viewer">Não foi possível encontrar um visualizador para <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Baixar a mensagem completa</string>
|
||||
<string name="message_view_sender_label">via %1$s</string>
|
||||
|
@ -725,10 +724,6 @@ Por favor encaminhe relatórios de bugs, contribua com novos recursos e tire dú
|
|||
<string name="font_size_message_list_preview">Visualização</string>
|
||||
<string name="font_size_message_view">Mensagens</string>
|
||||
<string name="font_size_message_view_sender">Remetente</string>
|
||||
<string name="font_size_message_view_to">Para</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Cco</string>
|
||||
<string name="font_size_message_view_additional_headers">Cabeçalhos adicionais</string>
|
||||
<string name="font_size_message_view_subject">Assunto</string>
|
||||
<string name="font_size_message_view_date">Data e hora</string>
|
||||
<string name="font_size_message_view_content">Corpo da mensagem</string>
|
||||
|
|
|
@ -227,7 +227,6 @@ Por favor envie relatórios de falhas, contribua com novas funcionalidades e col
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Não foi possível guardar o anexo</string>
|
||||
<string name="message_view_show_pictures_action">Mostrar imagens</string>
|
||||
<string name="message_view_no_viewer">Incapaz de encontrar visualizador para <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Transferir mensagem completa</string>
|
||||
<string name="message_view_sender_label">via %1$s</string>
|
||||
|
@ -722,10 +721,6 @@ Por favor envie relatórios de falhas, contribua com novas funcionalidades e col
|
|||
<string name="font_size_message_list_preview">Pré-visualização</string>
|
||||
<string name="font_size_message_view">Mensagens</string>
|
||||
<string name="font_size_message_view_sender">Remetente</string>
|
||||
<string name="font_size_message_view_to">Para</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Cabeçalhos adicionais</string>
|
||||
<string name="font_size_message_view_subject">Assunto</string>
|
||||
<string name="font_size_message_view_date">Data e hora</string>
|
||||
<string name="font_size_message_view_content">Corpo da mensagem</string>
|
||||
|
|
|
@ -226,7 +226,6 @@ cel mult încă <xliff:g id="messages_to_load">%d</xliff:g></string>
|
|||
<string name="message_view_cc_label">Cc:</string>
|
||||
<string name="message_view_bcc_label">Bcc:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Nu se poate salva atașamentul.</string>
|
||||
<string name="message_view_show_pictures_action">Arată pozele</string>
|
||||
<string name="message_view_no_viewer">Nu se poate găsi vizualizator pentru <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Descarcă mesajul complet</string>
|
||||
<string name="message_view_sender_label">via %1$s</string>
|
||||
|
@ -728,10 +727,6 @@ Uneori datorită faptului că cineva încearcă să te atace pe tine sau serveru
|
|||
<string name="font_size_message_list_preview">Previzualizare</string>
|
||||
<string name="font_size_message_view">Mesaje</string>
|
||||
<string name="font_size_message_view_sender">Expeditor</string>
|
||||
<string name="font_size_message_view_to">Către</string>
|
||||
<string name="font_size_message_view_cc">Cc</string>
|
||||
<string name="font_size_message_view_bcc">Bcc</string>
|
||||
<string name="font_size_message_view_additional_headers">Antete adiționale</string>
|
||||
<string name="font_size_message_view_subject">Subiect</string>
|
||||
<string name="font_size_message_view_date">Timp și dată</string>
|
||||
<string name="font_size_message_view_content">Conținut mesaj</string>
|
||||
|
|
|
@ -227,7 +227,6 @@ K-9 Mail — почтовый клиент для Android.
|
|||
<string name="message_view_cc_label">Копия:</string>
|
||||
<string name="message_view_bcc_label">Скрытая копия:</string>
|
||||
<string name="message_view_status_attachment_not_saved">Не получается сохранить вложение.</string>
|
||||
<string name="message_view_show_pictures_action">Изображения</string>
|
||||
<string name="message_view_no_viewer">Отсутствует просмотрщик <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Загрузить полностью</string>
|
||||
<string name="message_view_sender_label">от %1$s</string>
|
||||
|
@ -727,10 +726,6 @@ K-9 Mail — почтовый клиент для Android.
|
|||
<string name="font_size_message_list_preview">Просмотр</string>
|
||||
<string name="font_size_message_view">Сообщение</string>
|
||||
<string name="font_size_message_view_sender">Отправитель</string>
|
||||
<string name="font_size_message_view_to">Кому</string>
|
||||
<string name="font_size_message_view_cc">Копия</string>
|
||||
<string name="font_size_message_view_bcc">Скрытая</string>
|
||||
<string name="font_size_message_view_additional_headers">Все заголовки</string>
|
||||
<string name="font_size_message_view_subject">Тема</string>
|
||||
<string name="font_size_message_view_date">Дата и время</string>
|
||||
<string name="font_size_message_view_content">Тело сообщения</string>
|
||||
|
|
|
@ -209,7 +209,6 @@ Prosím, nahlasujte prípadné chyby, prispievajte novými funkciami a pýtajte
|
|||
<string name="message_view_cc_label">Kópia:</string>
|
||||
<string name="message_view_bcc_label">Skrytá kópia (Bcc):</string>
|
||||
<string name="message_view_status_attachment_not_saved">Prílohu sa nepodarilo uložiť.</string>
|
||||
<string name="message_view_show_pictures_action">Zobraziť obrázky</string>
|
||||
<string name="message_view_no_viewer">Nemožno nájsť prehliadač pre <xliff:g id="mimetype">%s</xliff:g>.</string>
|
||||
<string name="message_view_download_remainder">Stiahnuť kompletnú správu</string>
|
||||
<string name="message_view_sender_label">cez %1$s</string>
|
||||
|
@ -663,10 +662,6 @@ Prosím, nahlasujte prípadné chyby, prispievajte novými funkciami a pýtajte
|
|||
<string name="font_size_message_list_preview">Náhľad</string>
|
||||
<string name="font_size_message_view">Správy</string>
|
||||
<string name="font_size_message_view_sender">Odosielateľ</string>
|
||||
<string name="font_size_message_view_to">Komu</string>
|
||||
<string name="font_size_message_view_cc">Kópia</string>
|
||||
<string name="font_size_message_view_bcc">Skrytá kópia</string>
|
||||
<string name="font_size_message_view_additional_headers">Prídavné hlavičky</string>
|
||||
<string name="font_size_message_view_subject">Skrytá kópia</string>
|
||||
<string name="font_size_message_view_date">Čas a dátum</string>
|
||||
<string name="font_size_message_view_content">Telo správy</string>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue