From 08cca9bf7b7a77dc42393a22d90c145c38bb034d Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 14 Nov 2024 09:59:55 +0200 Subject: [PATCH] Added updating the thread details during swipe-to-refresh and other updates.| #74 --- .../email/api/email/gmail/GmailApiHelper.kt | 72 ++++--------------- .../api/email/gmail/GmailHistoryHandler.kt | 26 +------ .../email/database/entity/MessageEntity.kt | 24 +++++++ .../api/services/gmail/model/ThreadExt.kt | 28 +++++++- .../viewmodel/ThreadDetailsViewModel.kt | 53 ++++++++++---- 5 files changed, 105 insertions(+), 98 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt index a3dc554fe..2001f9ba6 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt @@ -22,15 +22,9 @@ import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.database.entity.AttachmentEntity import com.flowcrypt.email.database.entity.MessageEntity import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.disposition -import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.extractSubject import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.getAttachmentInfoList -import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.getDraftsCount -import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.getUniqueLabelsSet -import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.getUniqueRecipients -import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.hasAttachments -import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.hasPgp -import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.hasUnreadMessages import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.isMimeType +import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.toThreadInfo import com.flowcrypt.email.extensions.java.lang.printStackTraceIfDebugOnly import com.flowcrypt.email.extensions.uid import com.flowcrypt.email.model.KeyImportDetails @@ -419,8 +413,7 @@ class GmailApiHelper { responseHeaders: HttpHeaders? ) { t?.let { thread -> - val gmailThreadInfo = extractGmailThreadInfo(accountEntity, thread, context) - listResult.add(gmailThreadInfo) + listResult.add(thread.toThreadInfo(context, accountEntity)) } } @@ -442,39 +435,26 @@ class GmailApiHelper { format: String = RESPONSE_FORMAT_FULL, metadataHeaders: List? = null, fields: List? = null - ): GmailThreadInfo = withContext(Dispatchers.IO) + ): GmailThreadInfo? = withContext(Dispatchers.IO) { - val gmailApiService = generateGmailApiService(context, accountEntity) - - val request = gmailApiService - .users() - .threads() - .get(DEFAULT_USER_ID, threadId) - .setFormat(format) - - metadataHeaders?.let { metadataHeaders -> - request.metadataHeaders = metadataHeaders - } - - fields?.let { fields -> - request.fields = fields.joinToString(separator = ",") - } - - val thread = request.execute() - val gmailThreadInfo = extractGmailThreadInfo(accountEntity, thread, context) - - return@withContext gmailThreadInfo + return@withContext getThread( + context = context, + accountEntity = accountEntity, + threadId = threadId, + format = format, + metadataHeaders = metadataHeaders, + fields = fields + )?.toThreadInfo(context, accountEntity) } - suspend fun loadMessagesInThread( + suspend fun getThread( context: Context, accountEntity: AccountEntity, threadId: String, format: String = RESPONSE_FORMAT_FULL, metadataHeaders: List? = null, fields: List? = null - ): List = withContext(Dispatchers.IO) - { + ): Thread? = withContext(Dispatchers.IO) { val gmailApiService = generateGmailApiService(context, accountEntity) val request = gmailApiService @@ -491,7 +471,7 @@ class GmailApiHelper { request.fields = fields.joinToString(separator = ",") } - return@withContext request.execute()?.messages ?: emptyList() + return@withContext request.execute() } suspend fun loadMsgs( @@ -1291,29 +1271,5 @@ class GmailApiHelper { } ?: e } } - - private fun extractGmailThreadInfo( - accountEntity: AccountEntity, - thread: Thread, - context: Context - ): GmailThreadInfo { - val receiverEmail = accountEntity.email - val gmailThreadInfo = GmailThreadInfo( - id = thread.id, - lastMessage = requireNotNull( - thread.messages?.lastOrNull { - !it.labelIds.contains(LABEL_DRAFT) - } ?: thread.messages.first()), - messagesCount = thread.messages?.size ?: 0, - draftsCount = thread.getDraftsCount(), - recipients = thread.getUniqueRecipients(receiverEmail), - subject = thread.extractSubject(context, receiverEmail), - labels = thread.getUniqueLabelsSet(), - hasAttachments = thread.hasAttachments(), - hasPgpThings = thread.hasPgp(), - hasUnreadMessages = thread.hasUnreadMessages() - ) - return gmailThreadInfo - } } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailHistoryHandler.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailHistoryHandler.kt index 7c7e85ff0..cda38a9a1 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailHistoryHandler.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailHistoryHandler.kt @@ -13,7 +13,6 @@ import com.flowcrypt.email.api.email.gmail.GmailApiHelper.Companion.LABEL_TRASH import com.flowcrypt.email.api.email.gmail.GmailApiHelper.Companion.labelsToImapFlags import com.flowcrypt.email.api.email.gmail.api.GmaiAPIMimeMessage import com.flowcrypt.email.api.email.model.LocalFolder -import com.flowcrypt.email.api.email.model.MessageFlag import com.flowcrypt.email.database.FlowCryptRoomDatabase import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.database.entity.MessageEntity @@ -182,9 +181,7 @@ object GmailHistoryHandler { threadDraftsCount = thread.draftsCount, labelIds = thread.labels.joinToString(separator = LABEL_IDS_SEPARATOR), hasAttachments = thread.hasAttachments, - fromAddresses = InternetAddress.toString( - thread.recipients.toTypedArray() - ), + fromAddresses = InternetAddress.toString(thread.recipients.toTypedArray()), hasPgp = thread.hasPgpThings ) } @@ -211,26 +208,7 @@ object GmailHistoryHandler { onlyPgpModeEnabled = accountEntity.showOnlyEncrypted ?: false ) { message, messageEntity -> if (message.threadId == thread.id) { - messageEntity.copy( - id = threadMessageEntity.id, - uid = threadMessageEntity.uid, - subject = thread.subject, - threadMessagesCount = thread.messagesCount, - threadDraftsCount = thread.draftsCount, - labelIds = thread.labels.joinToString(separator = LABEL_IDS_SEPARATOR), - hasAttachments = thread.hasAttachments, - fromAddresses = InternetAddress.toString(thread.recipients.toTypedArray()), - hasPgp = thread.hasPgpThings, - flags = if (thread.hasUnreadMessages) { - threadMessageEntity.flags?.replace(MessageFlag.SEEN.value, "") - } else { - if (threadMessageEntity.flags?.contains(MessageFlag.SEEN.value) == true) { - threadMessageEntity.flags - } else { - threadMessageEntity.flags.plus("${MessageFlag.SEEN.value} ") - } - } - ) + messageEntity.toUpdatedThreadCopy(threadMessageEntity, thread) } else messageEntity } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/database/entity/MessageEntity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/database/entity/MessageEntity.kt index 523f45da6..fd4443caf 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/database/entity/MessageEntity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/database/entity/MessageEntity.kt @@ -30,6 +30,7 @@ import com.flowcrypt.email.api.email.EmailUtil import com.flowcrypt.email.api.email.FoldersManager import com.flowcrypt.email.api.email.JavaEmailConstants import com.flowcrypt.email.api.email.gmail.api.GmaiAPIMimeMessage +import com.flowcrypt.email.api.email.gmail.model.GmailThreadInfo import com.flowcrypt.email.api.email.model.MessageFlag import com.flowcrypt.email.api.email.model.OutgoingMessageInfo import com.flowcrypt.email.database.MessageState @@ -460,6 +461,29 @@ data class MessageEntity( } else charSequence } + fun toUpdatedThreadCopy(messageEntity: MessageEntity, thread: GmailThreadInfo): MessageEntity { + return copy( + id = messageEntity.id, + uid = messageEntity.uid, + subject = thread.subject, + threadMessagesCount = thread.messagesCount, + threadDraftsCount = thread.draftsCount, + labelIds = thread.labels.joinToString(separator = LABEL_IDS_SEPARATOR), + hasAttachments = thread.hasAttachments, + fromAddresses = InternetAddress.toString(thread.recipients.toTypedArray()), + hasPgp = thread.hasPgpThings, + flags = if (thread.hasUnreadMessages) { + messageEntity.flags?.replace(MessageFlag.SEEN.value, "") + } else { + if (messageEntity.flags?.contains(MessageFlag.SEEN.value) == true) { + messageEntity.flags + } else { + messageEntity.flags.plus("${MessageFlag.SEEN.value} ") + } + } + ) + } + private fun prepareDateHeaderValue(context: Context): String { val dateInMilliseconds: Long = if (JavaEmailConstants.FOLDER_OUTBOX.equals(folder, ignoreCase = true)) { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt index 9b980eee2..d1bc15829 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt @@ -8,6 +8,9 @@ package com.flowcrypt.email.extensions.com.google.api.services.gmail.model import android.content.Context import com.flowcrypt.email.R import com.flowcrypt.email.api.email.gmail.GmailApiHelper +import com.flowcrypt.email.api.email.gmail.GmailApiHelper.Companion.LABEL_DRAFT +import com.flowcrypt.email.api.email.gmail.model.GmailThreadInfo +import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.extensions.kotlin.asInternetAddresses import com.google.api.services.gmail.model.Thread import jakarta.mail.internet.InternetAddress @@ -56,7 +59,7 @@ fun Thread.getUniqueLabelsSet(): Set { } fun Thread.getDraftsCount(): Int { - return messages?.filter { it.labelIds.contains(GmailApiHelper.LABEL_DRAFT) }?.size ?: 0 + return messages?.filter { it.labelIds.contains(LABEL_DRAFT) }?.size ?: 0 } fun Thread.hasUnreadMessages(): Boolean { @@ -90,4 +93,27 @@ fun Thread.extractSubject(context: Context, receiverEmail: String): String { }?.getSubject() ?: messages?.getOrNull(0)?.getSubject() ?: context.getString(R.string.no_subject) +} + +fun Thread.toThreadInfo( + context: Context, + accountEntity: AccountEntity +): GmailThreadInfo { + val receiverEmail = accountEntity.email + val gmailThreadInfo = GmailThreadInfo( + id = id, + lastMessage = requireNotNull( + messages?.lastOrNull { + !it.labelIds.contains(LABEL_DRAFT) + } ?: messages.first()), + messagesCount = messages?.size ?: 0, + draftsCount = getDraftsCount(), + recipients = getUniqueRecipients(receiverEmail), + subject = extractSubject(context, receiverEmail), + labels = getUniqueLabelsSet(), + hasAttachments = hasAttachments(), + hasPgpThings = hasPgp(), + hasUnreadMessages = hasUnreadMessages() + ) + return gmailThreadInfo } \ No newline at end of file diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt index 48eebf897..dee851541 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt @@ -11,6 +11,7 @@ import com.flowcrypt.email.api.email.FoldersManager import com.flowcrypt.email.api.email.JavaEmailConstants import com.flowcrypt.email.api.email.MsgsCacheManager import com.flowcrypt.email.api.email.gmail.GmailApiHelper +import com.flowcrypt.email.api.email.gmail.model.GmailThreadInfo import com.flowcrypt.email.api.email.model.LocalFolder import com.flowcrypt.email.api.email.model.MessageFlag import com.flowcrypt.email.api.retrofit.response.base.Result @@ -20,6 +21,7 @@ import com.flowcrypt.email.database.entity.MessageEntity import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.getInReplyTo import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.getMessageId import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.isDraft +import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.toThreadInfo import com.flowcrypt.email.extensions.java.lang.printStackTraceIfDebugOnly import com.flowcrypt.email.model.MessageAction import com.flowcrypt.email.ui.adapter.MessagesInThreadListAdapter @@ -369,15 +371,32 @@ class ThreadDetailsViewModel( if (threadMessageEntity.threadIdAsHEX.isNullOrEmpty() || !activeAccount.isGoogleSignInAccount) { return Result.success(Data(silentUpdate = silentUpdate, list = listOf())) } else { - val threadHeader = - prepareThreadHeader(roomDatabase.msgDao().getMsgById(threadMessageEntityId)) - try { - val messagesInThread = GmailApiHelper.loadMessagesInThread( - getApplication(), - activeAccount, - threadMessageEntity.threadIdAsHEX - ).toMutableList().apply { + val thread = GmailApiHelper.getThread( + context = getApplication(), + accountEntity = activeAccount, + threadId = threadMessageEntity.threadIdAsHEX, + format = GmailApiHelper.RESPONSE_FORMAT_FULL + ) ?: error("Thread not found") + + val threadInfo = thread.toThreadInfo(getApplication(), activeAccount) + //keep thread info updated in the local cache + roomDatabase.msgDao().updateSuspend( + MessageEntity.genMessageEntities( + context = getApplication(), + account = activeAccount.email, + accountType = activeAccount.accountType, + label = localFolder.fullName, + msgsList = listOf(threadInfo.lastMessage), + isNew = false, + onlyPgpModeEnabled = activeAccount.showOnlyEncrypted ?: false + ) { message, messageEntity -> + if (message.threadId == threadMessageEntity.threadIdAsHEX) { + messageEntity.toUpdatedThreadCopy(threadMessageEntity, threadInfo) + } else messageEntity + }) + + val messagesInThread = (thread.messages ?: emptyList()).toMutableList().apply { //try to put drafts in the right position val drafts = filter { it.isDraft() } drafts.forEach { draft -> @@ -406,10 +425,6 @@ class ThreadDetailsViewModel( ).associateBy({ it.message.id }, { it.id }) } else emptyMap() - //update the actual thread size - roomDatabase.msgDao() - .updateSuspend(threadMessageEntity.copy(threadMessagesCount = messagesInThread.size)) - val isOnlyPgpModeEnabled = activeAccount.showOnlyEncrypted ?: false val messageEntitiesBasedOnServerResult = MessageEntity.genMessageEntities( context = getApplication(), @@ -503,6 +518,11 @@ class ThreadDetailsViewModel( } ?: messageItemBasedOnDataFromServer } + val threadHeader = prepareThreadHeader( + messageEntity = roomDatabase.msgDao().getMsgById(threadMessageEntityId), + threadInfo = threadInfo + ) + return Result.success( Data( silentUpdate = silentUpdate, @@ -516,7 +536,10 @@ class ThreadDetailsViewModel( } } - private suspend fun prepareThreadHeader(messageEntity: MessageEntity?): MessagesInThreadListAdapter.ThreadHeader = + private suspend fun prepareThreadHeader( + messageEntity: MessageEntity?, + threadInfo: GmailThreadInfo? = null + ): MessagesInThreadListAdapter.ThreadHeader = withContext(Dispatchers.IO) { val account = getActiveAccountSuspend() ?: return@withContext MessagesInThreadListAdapter.ThreadHeader( @@ -530,13 +553,13 @@ class ThreadDetailsViewModel( return@withContext try { //try to get the last changes from a server - val latestLabelIds = GmailApiHelper.loadThreadInfo( + val latestLabelIds = (threadInfo ?: GmailApiHelper.loadThreadInfo( context = getApplication(), accountEntity = account, threadId = messageEntity?.threadIdAsHEX ?: "", fields = listOf("id", "messages/labelIds"), format = GmailApiHelper.RESPONSE_FORMAT_MINIMAL - ).labels + ))?.labels ?: error("Thread not found") if (cachedLabelIds == null || !(latestLabelIds.containsAll(cachedLabelIds) && cachedLabelIds.containsAll(latestLabelIds))