Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,8 @@ class GmailApiHelper {
t: Thread?,
responseHeaders: HttpHeaders?
) {
t?.let { thread ->
listResult.add(thread.toThreadInfo(context, accountEntity, localFolder))
t.toThreadInfo(context, accountEntity, localFolder)?.let { threadInfo ->
listResult.add(threadInfo)
}
}

Expand Down Expand Up @@ -453,7 +453,7 @@ class GmailApiHelper {
format = format,
metadataHeaders = metadataHeaders,
fields = fields
)?.toThreadInfo(context, accountEntity, localFolder)
).toThreadInfo(context, accountEntity, localFolder)
}

suspend fun getThread(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ import jakarta.mail.internet.InternetAddress
/**
* @author Denys Bondarenko
*/
fun Message.hasPgp(): Boolean {
val baseContentType = payload?.headers?.firstOrNull {
fun Message?.hasPgp(): Boolean {
val baseContentType = this?.payload?.headers?.firstOrNull {
it?.name == "Content-Type"
}?.value?.asContentTypeOrNull()

/**
* based on https://datatracker.ietf.org/doc/html/rfc3156#section-5
*/
val isOpenPGPMimeSigned = payload?.parts?.size == 2
val isOpenPGPMimeSigned = this?.payload?.parts?.size == 2
&& "multipart/signed" == baseContentType?.baseType?.lowercase()
&& baseContentType.getParameter("protocol")?.lowercase() == "application/pgp-signature"
&& baseContentType.getParameter("micalg")?.lowercase()?.startsWith("pgp-") == true
Expand All @@ -36,68 +36,68 @@ fun Message.hasPgp(): Boolean {
* based on https://datatracker.ietf.org/doc/html/rfc3156#section-4
*/
val isOpenPGPMimeEncrypted = !isOpenPGPMimeSigned
&& payload?.parts?.size == 2
&& this?.payload?.parts?.size == 2
&& "multipart/encrypted" == baseContentType?.baseType?.lowercase()
&& baseContentType.getParameter("protocol")?.lowercase() == "application/pgp-encrypted"

val hasEncryptedParts = payload?.parts?.any { it.hasPgp() } == true
val hasEncryptedParts = this?.payload?.parts?.any { it.hasPgp() } == true

return EmailUtil.hasEncryptedData(snippet)
|| EmailUtil.hasSignedData(snippet)
return EmailUtil.hasEncryptedData(this?.snippet)
|| EmailUtil.hasSignedData(this?.snippet)
|| isOpenPGPMimeSigned
|| isOpenPGPMimeEncrypted
|| hasEncryptedParts
}

fun Message.getRecipients(vararg recipientType: String): List<InternetAddress> {
return payload?.headers?.firstOrNull { header ->
fun Message?.getRecipients(vararg recipientType: String): List<InternetAddress> {
return this?.payload?.headers?.firstOrNull { header ->
header?.name in recipientType
}?.value?.asInternetAddresses()?.toList() ?: emptyList()
}

fun Message.getSubject(): String? {
return payload?.headers?.firstOrNull { header ->
fun Message?.getSubject(): String? {
return this?.payload?.headers?.firstOrNull { header ->
header?.name == "Subject"
}?.value
}

fun Message.getInReplyTo(): String? {
return payload?.headers?.firstOrNull { header ->
fun Message?.getInReplyTo(): String? {
return this?.payload?.headers?.firstOrNull { header ->
header?.name == JavaEmailConstants.HEADER_IN_REPLY_TO
}?.value
}

fun Message.getMessageId(): String? {
return payload?.headers?.firstOrNull { header ->
fun Message?.getMessageId(): String? {
return this?.payload?.headers?.firstOrNull { header ->
header?.name == JavaEmailConstants.HEADER_MESSAGE_ID
}?.value
}

fun Message.isDraft(): Boolean {
return labelIds?.contains(GmailApiHelper.LABEL_DRAFT) == true
fun Message?.isDraft(): Boolean {
return this?.labelIds?.contains(GmailApiHelper.LABEL_DRAFT) == true
}

fun Message.hasAttachments(): Boolean {
return payload?.hasAttachments() == true
fun Message?.hasAttachments(): Boolean {
return this?.payload?.hasAttachments() == true
}

fun Message.filterHeadersWithName(name: String): List<MessagePartHeader> {
return payload?.headers?.filter { header -> header?.name == name } ?: emptyList()
fun Message?.filterHeadersWithName(name: String): List<MessagePartHeader> {
return this?.payload?.headers?.filter { header -> header?.name == name } ?: emptyList()
}

fun Message.containsLabel(localFolder: LocalFolder?): Boolean? {
return labelIds?.contains(localFolder?.fullName)
fun Message?.containsLabel(localFolder: LocalFolder?): Boolean? {
return this?.labelIds?.contains(localFolder?.fullName)
}

fun Message.isTrashed(): Boolean {
return labelIds?.contains(GmailApiHelper.LABEL_TRASH) == true
fun Message?.isTrashed(): Boolean {
return this?.labelIds?.contains(GmailApiHelper.LABEL_TRASH) == true
}

fun Message.isSent(): Boolean {
return labelIds?.contains(GmailApiHelper.LABEL_SENT) == true
fun Message?.isSent(): Boolean {
return this?.labelIds?.contains(GmailApiHelper.LABEL_SENT) == true
}

fun Message.canBeUsed(localFolder: LocalFolder?): Boolean {
fun Message?.canBeUsed(localFolder: LocalFolder?): Boolean {
return if (localFolder?.getFolderType() == FoldersManager.FolderType.TRASH) {
isTrashed() == true
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import jakarta.mail.internet.InternetAddress
/**
* @author Denys Bondarenko
*/
fun Thread.getUniqueRecipients(account: String, localFolder: LocalFolder?): List<InternetAddress> {
fun Thread?.getUniqueRecipients(account: String, localFolder: LocalFolder?): List<InternetAddress> {
val threadMessages = this?.messages
return mutableListOf<InternetAddress>().apply {
val filteredMessages = messages?.filter {
val filteredMessages = threadMessages?.filter {
it.canBeUsed(localFolder)
}?.takeIf {
}?.filterNotNull()?.takeIf {
it.isNotEmpty()
} ?: return@apply
val fromHeaderName = "From"
Expand Down Expand Up @@ -69,42 +70,43 @@ fun Thread.getUniqueRecipients(account: String, localFolder: LocalFolder?): List
}
}

fun Thread.getUniqueLabelsSet(localFolder: LocalFolder?): Set<String> {
return messages?.filter {
it.canBeUsed(localFolder)
fun Thread?.getUniqueLabelsSet(localFolder: LocalFolder?): Set<String> {
return this?.messages?.filter {
it?.canBeUsed(localFolder) == true
}?.flatMap {
it.labelIds ?: emptyList()
it?.labelIds ?: emptyList()
}?.toSortedSet() ?: emptySet()
}

fun Thread.getDraftsCount(localFolder: LocalFolder?): Int {
return messages?.filter {
it.canBeUsed(localFolder) && it.labelIds.contains(LABEL_DRAFT)
fun Thread?.getDraftsCount(localFolder: LocalFolder?): Int {
return this?.messages?.filter {
it?.canBeUsed(localFolder) == true && it.labelIds?.contains(LABEL_DRAFT) == true
}?.size ?: 0
}

fun Thread.hasUnreadMessages(localFolder: LocalFolder?): Boolean {
return messages?.filter {
it.canBeUsed(localFolder)
fun Thread?.hasUnreadMessages(localFolder: LocalFolder?): Boolean {
return this?.messages?.filter {
it?.canBeUsed(localFolder) == true
}?.any {
it.labelIds?.contains(GmailApiHelper.LABEL_UNREAD) == true
it?.labelIds?.contains(GmailApiHelper.LABEL_UNREAD) == true
} == true
}

fun Thread.hasAttachments(localFolder: LocalFolder?): Boolean {
return messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasAttachments() } == true
fun Thread?.hasAttachments(localFolder: LocalFolder?): Boolean {
return this?.messages?.filter { it?.canBeUsed(localFolder) == true }
?.any { it.hasAttachments() } == true
}

fun Thread.hasPgp(localFolder: LocalFolder?): Boolean {
return messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasPgp() } == true
fun Thread?.hasPgp(localFolder: LocalFolder?): Boolean {
return this?.messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasPgp() } == true
}

fun Thread.extractSubject(
fun Thread?.extractSubject(
context: Context,
receiverEmail: String,
localFolder: LocalFolder?
): String {
val filteredMessages = messages?.filter { it.canBeUsed(localFolder) }
val filteredMessages = this?.messages?.filter { it.canBeUsed(localFolder) }

return filteredMessages?.getOrNull(0)?.takeIf { message ->
(message.getRecipients("From").any { internetAddress ->
Expand All @@ -120,22 +122,25 @@ fun Thread.extractSubject(
?: context.getString(R.string.no_subject)
}

fun Thread.filteredMessages(localFolder: LocalFolder?): List<Message> {
return messages?.filter { it.canBeUsed(localFolder) } ?: emptyList()
fun Thread?.filteredMessages(localFolder: LocalFolder?): List<Message> {
return this?.messages?.filter { it.canBeUsed(localFolder) } ?: emptyList()
}

fun Thread.toThreadInfo(
fun Thread?.toThreadInfo(
context: Context,
accountEntity: AccountEntity,
localFolder: LocalFolder? = null
): GmailThreadInfo {
): GmailThreadInfo? {
val threadId = this?.id ?: return null
val receiverEmail = accountEntity.email
val lastMessage = messages?.lastOrNull {
!it.labelIds.contains(LABEL_DRAFT) && it.canBeUsed(localFolder)
} ?: messages?.first()
it?.labelIds?.contains(LABEL_DRAFT) == false && it.canBeUsed(localFolder)
}
?: messages?.firstOrNull()
?: return null
val gmailThreadInfo = GmailThreadInfo(
id = id,
lastMessage = requireNotNull(lastMessage),
id = threadId,
lastMessage = lastMessage,
messagesCount = messages?.filter { it.canBeUsed(localFolder) }?.size ?: 0,
draftsCount = getDraftsCount(localFolder),
recipients = getUniqueRecipients(receiverEmail, localFolder),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,9 @@ class ThreadDetailsViewModel(
format = GmailApiHelper.RESPONSE_FORMAT_FULL
) ?: error("Thread not found")

val threadInfo = thread.toThreadInfo(getApplication(), activeAccount, localFolder)
val threadInfo = requireNotNull(
thread.toThreadInfo(getApplication(), activeAccount, localFolder)
)

if (!threadInfo.labels.contains(localFolder.fullName) && !localFolder.isAll) {
val context: Context = getApplication()
Expand Down