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

feat(ComposeView): Download body for reply / forward #685

Merged
merged 4 commits into from
Apr 11, 2023
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
25 changes: 3 additions & 22 deletions Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ enum ActionsTarget: Equatable {
case .archive:
try await move(to: .archive)
case .forward:
try await reply(mode: .forward([]))
try await reply(mode: .forward)
case .markAsRead, .markAsUnread:
try await toggleRead()
case .move:
Expand Down Expand Up @@ -380,33 +380,14 @@ enum ActionsTarget: Equatable {
}

private func reply(mode: ReplyMode) async throws {
var completeMode = mode
switch target {
case let .threads(threads, _):
// We don't handle this action in multiple selection
guard threads.count == 1, let thread = threads.first,
let message = thread.messages.last(where: { !$0.isDraft }) else { break }
// Download message if needed to get body
if !message.fullyDownloaded {
try await mailboxManager.message(message: message)
}
if mode == .forward([]) {
let attachments = try await mailboxManager.apiFetcher.attachmentsToForward(
mailbox: mailboxManager.mailbox,
message: message
).attachments
completeMode = .forward(attachments)
}
replyHandler?(message, completeMode)
replyHandler?(message, mode)
case let .message(message):
if mode == .forward([]) {
let attachments = try await mailboxManager.apiFetcher.attachmentsToForward(
mailbox: mailboxManager.mailbox,
message: message
).attachments
completeMode = .forward(attachments)
}
replyHandler?(message, completeMode)
replyHandler?(message, mode)
}
}

Expand Down
7 changes: 3 additions & 4 deletions Mail/Views/New Message/Attachments/AttachmentsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,14 @@ class AttachmentsManager: ObservableObject {
init(draft: Draft, mailboxManager: MailboxManager) {
self.draft = draft
self.mailboxManager = mailboxManager

completeUploadedAttachments()
}

private func completeUploadedAttachments() {
func completeUploadedAttachments() {
for attachment in attachments {
var uploadTask = attachmentUploadTaskFor(uuid: attachment.uuid)
let uploadTask = attachmentUploadTaskFor(uuid: attachment.uuid)
uploadTask.progress = 1
}
objectWillChange.send()
}

private func updateAttachment(oldAttachment: Attachment, newAttachment: Attachment) {
Expand Down
92 changes: 74 additions & 18 deletions Mail/Views/New Message/ComposeMessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct ComposeMessageView: View {
@StateRealmObject var draft: Draft
@State private var editor = RichTextEditorModel()
@State private var showCc = false
@State private var isLoadingContent: Bool
@FocusState private var focusedField: ComposeViewFieldType?

@State private var addRecipientHandler: ((Recipient) -> Void)?
Expand All @@ -78,6 +79,8 @@ struct ComposeMessageView: View {

@StateObject private var alert = NewMessageAlert()

let messageReply: MessageReply?

private var isSendButtonDisabled: Bool {
return draft.identityId?.isEmpty == true
|| (draft.to.isEmpty && draft.cc.isEmpty && draft.bcc.isEmpty)
Expand All @@ -88,7 +91,8 @@ struct ComposeMessageView: View {
return (!autocompletion.isEmpty || !unknownRecipientAutocompletion.isEmpty) && focusedField != nil
}

private init(mailboxManager: MailboxManager, draft: Draft) {
private init(mailboxManager: MailboxManager, draft: Draft, messageReply: MessageReply? = nil) {
self.messageReply = messageReply
_mailboxManager = State(initialValue: mailboxManager)
if draft.identityId == nil || draft.identityId?.isEmpty == true,
let signature = mailboxManager.getSignatureResponse() {
Expand All @@ -105,6 +109,7 @@ struct ComposeMessageView: View {
_draft = StateRealmObject(wrappedValue: draft)
_showCc = State(initialValue: !draft.bcc.isEmpty || !draft.cc.isEmpty)
_attachmentsManager = StateObject(wrappedValue: AttachmentsManager(draft: draft, mailboxManager: mailboxManager))
_isLoadingContent = State(initialValue: (draft.messageUid != nil && draft.remoteUUID.isEmpty) || messageReply != nil)
}

var body: some View {
Expand Down Expand Up @@ -155,7 +160,7 @@ struct ComposeMessageView: View {
}
}
.overlay {
if draft.messageUid != nil && draft.remoteUUID.isEmpty {
if isLoadingContent {
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(MailResourcesAsset.backgroundColor.swiftUIColor)
Expand Down Expand Up @@ -214,9 +219,9 @@ struct ComposeMessageView: View {
}
.customAlert(isPresented: $alert.isShowing) {
switch alert.state {
case let .link(handler):
case .link(let handler):
AddLinkView(actionHandler: handler)
case let .emptySubject(handler):
case .emptySubject(let handler):
EmptySubjectView(actionHandler: handler)
case .none:
EmptyView()
Expand All @@ -228,16 +233,10 @@ struct ComposeMessageView: View {
}
}
.task {
guard draft.messageUid != nil && draft.remoteUUID.isEmpty else { return }

do {
if let fetchedDraft = try await mailboxManager.draft(partialDraft: draft),
let liveFetchedDraft = fetchedDraft.thaw() {
self.draft = liveFetchedDraft
}
} catch {
// Fail silently
}
await prepareCompleteDraft()
}
.task {
await prepareReplyForwardBodyAndAttachments()
}
.navigationViewStyle(.stack)
.defaultAppStorage(.shared)
Expand Down Expand Up @@ -304,6 +303,65 @@ struct ComposeMessageView: View {
}
dismiss()
}

private func prepareCompleteDraft() async {
guard draft.messageUid != nil && draft.remoteUUID.isEmpty else { return }

do {
if let fetchedDraft = try await mailboxManager.draft(partialDraft: draft),
let liveFetchedDraft = fetchedDraft.thaw() {
draft = liveFetchedDraft
}
isLoadingContent = false
} catch {
dismiss()
IKSnackBar.showSnackBar(message: MailError.unknownError.localizedDescription)
}
}

private func prepareReplyForwardBodyAndAttachments() async {
guard let messageReply else { return }

let prepareBodyTask = Task {
try await prepareBody(message: messageReply.message, replyMode: messageReply.replyMode)
}

let prepareAttachmentsTask = Task {
try await prepareAttachments(message: messageReply.message, replyMode: messageReply.replyMode)
}

do {
_ = try await prepareBodyTask.value
_ = try await prepareAttachmentsTask.value
isLoadingContent = false
} catch {
dismiss()
IKSnackBar.showSnackBar(message: MailError.unknownError.localizedDescription)
}
}

private func prepareBody(message: Message, replyMode: ReplyMode) async throws {
if !message.fullyDownloaded {
try await mailboxManager.message(message: message)
}

guard let freshMessage = message.thaw() else { return }
freshMessage.realm?.refresh()
$draft.body.wrappedValue = Draft.replyingBody(message: freshMessage, replyMode: replyMode)
}

private func prepareAttachments(message: Message, replyMode: ReplyMode) async throws {
guard replyMode == .forward else { return }
let attachments = try await mailboxManager.apiFetcher.attachmentsToForward(
mailbox: mailboxManager.mailbox,
message: message
).attachments

for attachment in attachments {
$draft.attachments.append(attachment)
}
attachmentsManager.completeUploadedAttachments()
}
}

extension ComposeMessageView {
Expand All @@ -312,12 +370,10 @@ extension ComposeMessageView {
}

static func replyOrForwardMessage(messageReply: MessageReply, mailboxManager: MailboxManager) -> ComposeMessageView {
let message = messageReply.message
// If message doesn't exist anymore try to show the frozen one
let freshMessage = message.thaw() ?? message
return ComposeMessageView(
mailboxManager: mailboxManager,
draft: .replying(to: freshMessage, mode: messageReply.replyMode, localDraftUUID: messageReply.localDraftUUID)
draft: .replying(reply: messageReply),
messageReply: messageReply
)
}

Expand Down
18 changes: 6 additions & 12 deletions Mail/Views/Thread/ThreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct ThreadView: View {
@StateObject private var moveSheet = MoveSheet()
@StateObject private var bottomSheet = MessageBottomSheet()
@StateObject private var threadBottomSheet = ThreadBottomSheet()

@State private var showEmptyView = false

@EnvironmentObject var globalBottomSheet: GlobalBottomSheet
Expand Down Expand Up @@ -136,20 +136,20 @@ struct ThreadView: View {
ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager)
}
.sheet(isPresented: $moveSheet.isShowing) {
if case let .move(folderId, handler) = moveSheet.state {
if case .move(let folderId, let handler) = moveSheet.state {
MoveEmailView.sheetView(mailboxManager: mailboxManager, from: folderId, moveHandler: handler)
}
}
.floatingPanel(state: bottomSheet) {
switch bottomSheet.state {
case let .contact(recipient, isRemote):
case .contact(let recipient, let isRemote):
ContactActionsView(
recipient: recipient,
isRemoteContact: isRemote,
bottomSheet: bottomSheet,
mailboxManager: mailboxManager
)
case let .replyOption(message, isThread):
case .replyOption(let message, let isThread):
ReplyActionsView(
mailboxManager: mailboxManager,
target: isThread ? .threads([thread], false) : .message(message),
Expand All @@ -164,7 +164,7 @@ struct ThreadView: View {
}
}
.floatingPanel(state: threadBottomSheet, halfOpening: true) {
if case let .actions(target) = threadBottomSheet.state, !target.isInvalidated {
if case .actions(let target) = threadBottomSheet.state, !target.isInvalidated {
ActionsView(mailboxManager: mailboxManager,
target: target,
state: threadBottomSheet,
Expand Down Expand Up @@ -206,13 +206,7 @@ struct ThreadView: View {
}
case .forward:
guard let message = thread.messages.last else { return }
Task {
let attachments = try await mailboxManager.apiFetcher.attachmentsToForward(
mailbox: mailboxManager.mailbox,
message: message
).attachments
messageReply = MessageReply(message: message, replyMode: .forward(attachments))
}
messageReply = MessageReply(message: message, replyMode: .forward)
case .archive:
Task {
await tryOrDisplayError {
Expand Down
51 changes: 23 additions & 28 deletions MailCore/Models/Draft.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,11 @@ public enum SaveDraftOption: String, Codable, PersistableEnum {

public enum ReplyMode: Equatable {
case reply, replyAll
case forward([Attachment])
case forward

var isReply: Bool {
return self == .reply || self == .replyAll
}

public static func == (lhs: ReplyMode, rhs: ReplyMode) -> Bool {
switch (lhs, rhs) {
case (.reply, .reply):
return true
case (.replyAll, .replyAll):
return true
case (.forward(_), .forward(_)):
return true
default:
return false
}
}
}

public struct DraftResponse: Codable {
Expand Down Expand Up @@ -194,22 +181,33 @@ public class Draft: Object, Decodable, Identifiable, Encodable {
return Draft(to: [recipient.detached()])
}

public static func replying(to message: Message, mode: ReplyMode, localDraftUUID: String) -> Draft {
var subject = "\(message.formattedSubject)"
public static func replyingBody(message: Message, replyMode: ReplyMode) -> String {
let unsafeQuote: String
var attachments: [Attachment] = []
switch replyMode {
case .reply, .replyAll:
unsafeQuote = Constants.replyQuote(message: message)
case .forward:
unsafeQuote = Constants.forwardQuote(message: message)
}

let quote = MessageBodyUtils.cleanHtmlContent(rawHtml: unsafeQuote) ?? ""

return "<br><br>" + quote
}

public static func replying(reply: MessageReply) -> Draft {
let message = reply.message
let mode = reply.replyMode
var subject = "\(message.formattedSubject)"
switch mode {
case .reply, .replyAll:
if !subject.starts(with: "Re: ") {
subject = "Re: \(subject)"
}
unsafeQuote = Constants.replyQuote(message: message)
case .forward(let attachmentsToForward):
case .forward:
if !subject.starts(with: "Fwd: ") {
subject = "Fwd: \(subject)"
}
unsafeQuote = Constants.forwardQuote(message: message)
attachments = attachmentsToForward.map { Attachment(value: $0) }
}

var recipientHolder = RecipientHolder()
Expand All @@ -218,18 +216,15 @@ public class Draft: Object, Decodable, Identifiable, Encodable {
recipientHolder = message.recipientsForReplyTo(replyAll: mode == .replyAll)
}

let quote = MessageBodyUtils.cleanHtmlContent(rawHtml: unsafeQuote) ?? ""

return Draft(localUUID: localDraftUUID,
return Draft(localUUID: reply.localDraftUUID,
inReplyToUid: mode.isReply ? message.uid : nil,
forwardedUid: mode == .forward([]) ? message.uid : nil,
forwardedUid: mode == .forward ? message.uid : nil,
references: "\(message.references ?? "") \(message.messageId ?? "")",
inReplyTo: message.messageId,
subject: subject,
body: "<br><br>\(quote)",
body: "",
to: recipientHolder.to,
cc: recipientHolder.cc,
attachments: attachments)
cc: recipientHolder.cc)
}

public func setSignature(_ signatureResponse: SignatureResponse) {
Expand Down