Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline images mail slow to display #758

Merged
merged 6 commits into from
May 25, 2023
Merged
Changes from 3 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
90 changes: 77 additions & 13 deletions Mail/Views/Thread/MessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,40 @@ import RealmSwift
import Shimmer
import SwiftUI

// TODO: move to Core
adrien-coye marked this conversation as resolved.
Show resolved Hide resolved
extension Sequence {
func asyncMap<T>(
_ transform: (Element) async throws -> T
) async rethrows -> [T] {
var values = [T]()

for element in self {
try await values.append(transform(element))
}

return values
}
}

// TODO: move to core
extension Sequence {
func asyncForEach(
_ operation: (Element) async throws -> Void
) async rethrows {
for element in self {
try await operation(element)
}
}
}

// TODO: move to core
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}

struct MessageView: View {
@ObservedRealmObject var message: Message
@State var presentableBody: PresentableBody
Expand Down Expand Up @@ -100,22 +134,52 @@ struct MessageView: View {
}

private func insertInlineAttachments() async throws {
let attachmentsArray = message.attachments.filter { $0.disposition == .inline }.toArray()
for attachment in attachmentsArray {
if let contentId = attachment.contentId {
let attachmentData = try await mailboxManager.attachmentData(attachment: attachment)

presentableBody.body?.value = presentableBody.body?.value?.replacingOccurrences(
of: "cid:\(contentId)",
with: "data:\(attachment.mimeType);base64,\(attachmentData.base64EncodedString())"
)
presentableBody.compactBody = presentableBody.compactBody?.replacingOccurrences(
of: "cid:\(contentId)",
with: "data:\(attachment.mimeType);base64,\(attachmentData.base64EncodedString())"
)
Task {
adrien-coye marked this conversation as resolved.
Show resolved Hide resolved
// Since mutation of the DOM is costly, I batch the processing of images, then mutate the DOM.
let attachmentsArray = message.attachments.filter { $0.disposition == .inline }.toArray()
let chunks = attachmentsArray.chunked(into: 10)

for chunk in chunks {
// Download images for the current chunk
let dataArray = try await chunk.asyncMap {
try await mailboxManager.attachmentData(attachment: $0)
}

// Read the DOM once
var body = presentableBody.body?.value
var compactBody = presentableBody.compactBody

// Prepare the new DOM with the loaded images
for (index, attachment) in chunk.enumerated() {
guard let contentId = attachment.contentId,
let data = dataArray[safe: index] else {
continue
}

body = body?.replacingOccurrences(
of: "cid:\(contentId)",
with: "data:\(attachment.mimeType);base64,\(data.base64EncodedString())"
)
compactBody = compactBody?.replacingOccurrences(
of: "cid:\(contentId)",
with: "data:\(attachment.mimeType);base64,\(data.base64EncodedString())"
)
}

// Mutate DOM
self.insertInlineAttachment(body: body, compactBody: compactBody)

// Delay between each chunk processing just enough, so the user feels the UI is responsive.
try await Task.sleep(nanoseconds: 4_000_000_000)
adrien-coye marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

/// Update the DOM in the main thread
@MainActor func insertInlineAttachment(body: String?, compactBody: String?) {
presentableBody.body?.value = body
presentableBody.compactBody = compactBody
}
}

struct MessageView_Previews: PreviewProvider {
Expand Down