Display embedded images for each message in a conversation
after they were loaded through the "Display embedded images" button for any message. There were some issues preventing the functionality from working which were addressed in this PR: - The same instance of MessageRenderer was not ready to work with different messages (messageBody was not changeable and the is a check that would stop execution when an image was "already rendered" which didn't take account that different messages can contain the same image) - When MessageRenderer returns the html, we need to display that through a new livedata which targets only the message currently opened, as resetting the whole ConversationUiModel causes UI glitches when the message is not the last one. MAILAND-1931
This commit is contained in:
parent
8ad3552fa1
commit
3cf1ac163d
|
@ -68,7 +68,7 @@ internal class MessageDetailsAdapter(
|
|||
private val onLoadEmbeddedImagesClicked: ((Message) -> Unit)?,
|
||||
private val onDisplayRemoteContentClicked: ((Message) -> Unit)?,
|
||||
private val userManager: UserManager,
|
||||
private val onLoadMessage: (Message) -> Unit,
|
||||
private val onLoadMessageBody: (Message) -> Unit,
|
||||
private val onAttachmentDownloadCallback: (Attachment) -> Unit
|
||||
) : ExpandableRecyclerAdapter<MessageDetailsAdapter.MessageDetailsListItem>(context) {
|
||||
|
||||
|
@ -179,7 +179,7 @@ internal class MessageDetailsAdapter(
|
|||
|
||||
Timber.v("Load data for message: ${message.messageId} at position $position")
|
||||
if (listItem.messageFormattedHtml == null) {
|
||||
onLoadMessage(message)
|
||||
onLoadMessageBody(message)
|
||||
}
|
||||
|
||||
val webView = itemView.messageWebViewContainer.getChildAt(0) as? WebView ?: return
|
||||
|
|
|
@ -68,16 +68,12 @@ internal class MessageRenderer(
|
|||
/** The [String] html of the message body */
|
||||
var messageBody: String? = null
|
||||
set(value) {
|
||||
// Return if body is already set
|
||||
if (field != null) return
|
||||
|
||||
// Update if value is not null
|
||||
if (value != null) field = value
|
||||
field = value
|
||||
// Clear inlined images to ensure when messageBody changes the loading of images doesn't get blocked
|
||||
// (messageBody changing means we're loading images for another message in the same conversation)
|
||||
inlinedImageIds.clear()
|
||||
}
|
||||
|
||||
/** reference to the [Document] */
|
||||
private val document by lazy { documentParser(messageBody!!) }
|
||||
|
||||
/** A [Channel] for receive new [EmbeddedImage] images to inline in [document] */
|
||||
val images = actor<List<EmbeddedImage>> {
|
||||
for (embeddedImages in channel) {
|
||||
|
@ -175,6 +171,10 @@ internal class MessageRenderer(
|
|||
private val imageInliner = actor<List<ImageString>> {
|
||||
for (imageStrings in channel) {
|
||||
|
||||
// Document is parsed for each emission because `messageBody`
|
||||
// field can change when switching messages in a conversation
|
||||
val document = documentParser(messageBody!!)
|
||||
|
||||
for (imageString in imageStrings) {
|
||||
|
||||
val (embeddedImage, image64) = imageString
|
||||
|
@ -194,14 +194,8 @@ internal class MessageRenderer(
|
|||
// Extract the message ID for which embedded images are being loaded
|
||||
// to pass it back to the caller along with the rendered body
|
||||
val messageId = imageStrings.firstOrNull()?.first?.messageId ?: continue
|
||||
documentStringifier.send(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
/** Actor that will stringify the [document] */
|
||||
private val documentStringifier = actor<String> {
|
||||
for (messageId in channel)
|
||||
renderedMessage.send(RenderedMessage(messageId, document.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
/** `CoroutineContext` for [idsListUpdater] for update [inlinedImageIds] of a single thread */
|
||||
|
@ -229,7 +223,7 @@ internal class MessageRenderer(
|
|||
private val imageDecoder: ImageDecoder = DefaultImageDecoder()
|
||||
) {
|
||||
|
||||
/** @return new instance of [MessageRenderer] with the given [messageBody] */
|
||||
/** @return new instance of [MessageRenderer] */
|
||||
fun create(scope: CoroutineScope) =
|
||||
MessageRenderer(dispatchers, documentParser, imageDecoder, attachmentsDirectory, scope)
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@ internal class MessageDetailsViewModel @Inject constructor(
|
|||
|
||||
private val _prepareEditMessageIntentResult: MutableLiveData<Event<IntentExtrasData>> = MutableLiveData()
|
||||
private val _decryptedConversationUiModel: MutableLiveData<ConversationUiModel> = MutableLiveData()
|
||||
private val _messageRenderedWithImages: MutableLiveData<Message> = MutableLiveData()
|
||||
private val _checkStoragePermission: MutableLiveData<Event<Boolean>> = MutableLiveData()
|
||||
private val _messageDetailsError: MutableLiveData<Event<String>> = MutableLiveData()
|
||||
|
||||
|
@ -222,6 +223,9 @@ internal class MessageDetailsViewModel @Inject constructor(
|
|||
val decryptedConversationUiModel: LiveData<ConversationUiModel>
|
||||
get() = _decryptedConversationUiModel
|
||||
|
||||
val messageRenderedWithImages: LiveData<Message>
|
||||
get() = _messageRenderedWithImages
|
||||
|
||||
private var areImagesDisplayed: Boolean = false
|
||||
|
||||
init {
|
||||
|
@ -230,7 +234,7 @@ internal class MessageDetailsViewModel @Inject constructor(
|
|||
val currentUiModel = _decryptedConversationUiModel.value
|
||||
val message = currentUiModel?.messages?.find { it.messageId == renderedMessage.messageId }
|
||||
message?.decryptedHTML = renderedMessage.renderedHtmlBody
|
||||
_decryptedConversationUiModel.value = currentUiModel
|
||||
_messageRenderedWithImages.value = message
|
||||
areImagesDisplayed = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,6 +190,15 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
|
|||
private fun continueSetup() {
|
||||
viewModel.conversationUiModel.observe(this) { viewModel.loadMailboxItemDetails() }
|
||||
viewModel.decryptedConversationUiModel.observe(this, ConversationUiModelObserver())
|
||||
viewModel.messageRenderedWithImages.observe(this) { message ->
|
||||
val messageId = message.messageId ?: return@observe
|
||||
messageExpandableAdapter.showMessageDetails(
|
||||
message.decryptedHTML,
|
||||
messageId,
|
||||
false,
|
||||
message.attachments
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.labels
|
||||
.onEach(messageExpandableAdapter::setAllLabels)
|
||||
|
@ -463,15 +472,15 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
|
|||
private inner class ConversationUiModelObserver : Observer<ConversationUiModel> {
|
||||
|
||||
override fun onChanged(conversation: ConversationUiModel) {
|
||||
val message = lastMessage(conversation)
|
||||
val lastMessage = conversation.messages.last()
|
||||
|
||||
displayToolbarData(conversation)
|
||||
setupLastMessageActionsListener(message)
|
||||
setupLastMessageActionsListener(lastMessage)
|
||||
|
||||
Timber.v("New decrypted message ${message.messageId}")
|
||||
Timber.v("New decrypted message ${lastMessage.messageId}")
|
||||
viewModel.renderedFromCache = AtomicBoolean(true)
|
||||
val decryptedBody = getDecryptedBody(message.decryptedHTML)
|
||||
if (decryptedBody.isEmpty() || message.messageBody.isNullOrEmpty()) {
|
||||
val decryptedBody = getDecryptedBody(lastMessage.decryptedHTML)
|
||||
if (decryptedBody.isEmpty() || lastMessage.messageBody.isNullOrEmpty()) {
|
||||
UiUtil.showInfoSnack(mSnackLayout, this@MessageDetailsActivity, R.string.decryption_error_desc).show()
|
||||
return
|
||||
}
|
||||
|
@ -548,8 +557,6 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
|
|||
expandedToolbarTitleTextView.text = subject
|
||||
}
|
||||
|
||||
private fun lastMessage(conversation: ConversationUiModel): Message = conversation.messages.last()
|
||||
|
||||
fun executeMessageAction(
|
||||
messageAction: Constants.MessageActionType,
|
||||
message: Message = requireNotNull(viewModel.decryptedConversationUiModel.value?.messages?.last())
|
||||
|
|
Loading…
Reference in New Issue