Skip to content

Commit

Permalink
Merge pull request #442 from Infomaniak/refactor-draft
Browse files Browse the repository at this point in the history
Refactor draft
  • Loading branch information
Lnamw authored Jan 13, 2023
2 parents 9cf9ada + 71dbe06 commit 1dcf373
Show file tree
Hide file tree
Showing 16 changed files with 420 additions and 695 deletions.
17 changes: 11 additions & 6 deletions Mail/Components/RecipientField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import MailCore
import MailResources
import RealmSwift
import SwiftUI
import WrappingHStack

Expand All @@ -41,7 +42,7 @@ struct RecipientChip: View {
}

struct RecipientField: View {
@Binding var recipients: [Recipient]
@Binding var recipients: RealmSwift.List<Recipient>
@Binding var autocompletion: [Recipient]
@Binding var addRecipientHandler: ((Recipient) -> Void)?
@FocusState var focusedField: ComposeViewFieldType?
Expand All @@ -54,9 +55,7 @@ struct RecipientField: View {
if !recipients.isEmpty {
WrappingHStack(recipients.indices, spacing: .constant(8), lineSpacing: 8) { i in
RecipientChip(recipient: recipients[i]) {
withAnimation {
_ = recipients.remove(at: i)
}
remove(recipientAt: i)
}
}
.alignmentGuide(.newMessageCellAlignment) { d in d[.top] + 21 }
Expand Down Expand Up @@ -96,17 +95,23 @@ struct RecipientField: View {

private func add(recipient: Recipient) {
withAnimation {
recipients.append(recipient)
$recipients.append(recipient)
}
currentText = ""
}

private func remove(recipientAt: Int) {
withAnimation {
$recipients.remove(at: recipientAt)
}
}
}

struct RecipientField_Previews: PreviewProvider {
static var previews: some View {
RecipientField(recipients: .constant([
PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3
]),
].toRealmList()),
autocompletion: .constant([]),
addRecipientHandler: .constant { _ in /* Preview */ },
focusedField: .init(),
Expand Down
11 changes: 1 addition & 10 deletions Mail/Helpers/RichTextEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ struct RichTextEditor: UIViewRepresentable {
@Binding var isShowingPhotoLibrary: Bool
var alert: ObservedObject<NewMessageAlert>.Wrapper

private var isFirstTime = true
private var delegateCount = 0
private var isInitialized: Bool {
delegateCount > 2
}

init(model: Binding<RichTextEditorModel>, body: Binding<String>,
alert: ObservedObject<NewMessageAlert>.Wrapper,
isShowingCamera: Binding<Bool>, isShowingFileSelection: Binding<Bool>, isShowingPhotoLibrary: Binding<Bool>) {
Expand Down Expand Up @@ -72,8 +66,6 @@ struct RichTextEditor: UIViewRepresentable {
}

func editor(_ editor: SQTextEditorView, cursorPositionDidChange position: SQEditorCursorPosition) {
parent.delegateCount += 1
guard parent.isInitialized else { return }
let newCursorPosition = CGFloat(position.bottom) + 20
if parent.model.cursorPosition != newCursorPosition {
parent.model.cursorPosition = newCursorPosition
Expand All @@ -89,10 +81,9 @@ struct RichTextEditor: UIViewRepresentable {
func editorContentChanged(_ editor: SQTextEditorView, content: String) {
var parentBody = parent.body.trimmingCharacters(in: .whitespacesAndNewlines)
parentBody = parentBody.replacingOccurrences(of: "\r", with: "")
if parentBody != content && !parent.isFirstTime {
if parentBody != content {
parent.body = content
}
parent.isFirstTime = false
}
}

Expand Down
26 changes: 12 additions & 14 deletions Mail/Utils/DraftUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,25 @@ import Foundation
import MailCore
import SwiftUI

@MainActor
class DraftUtils {
@MainActor public static func editDraft(from thread: Thread, mailboxManager: MailboxManager, editedMessageDraft: Binding<Draft?>) {
public static func editDraft(from thread: Thread, mailboxManager: MailboxManager, editedMessageDraft: Binding<Draft?>) {
guard let message = thread.messages.first else { return }
var sheetPresented = false

// If we already have the draft locally, present it directly
if let draft = mailboxManager.draft(messageUid: message.uid)?.detached() {
editedMessageDraft.wrappedValue = draft
sheetPresented = true
// Maybe it was an offline draft (If offline draft is created with draft.uuid = thread.uid)
} else if let localDraft = mailboxManager.draft(localUuid: thread.uid)?.detached() {
editedMessageDraft.wrappedValue = localDraft
sheetPresented = true
} else {
DraftUtils.editDraft(from: message, mailboxManager: mailboxManager, editedMessageDraft: editedMessageDraft)
}
}

// Update the draft
Task { [sheetPresented] in
let draft = try await mailboxManager.draft(from: message)
if !sheetPresented {
editedMessageDraft.wrappedValue = draft
}
public static func editDraft(from message: Message, mailboxManager: MailboxManager, editedMessageDraft: Binding<Draft?>) {
// If we already have the draft locally, present it directly
if let draft = mailboxManager.draft(messageUid: message.uid)?.detached() {
editedMessageDraft.wrappedValue = draft
// Draft comes from API, we will update it after showing the ComposeMessageView
} else {
editedMessageDraft.wrappedValue = Draft(messageUid: message.uid)
}
}
}
123 changes: 61 additions & 62 deletions Mail/Views/New Message/ComposeMessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ struct ComposeMessageView: View {
@Environment(\.dismiss) private var dismiss

@State private var mailboxManager: MailboxManager
@State private var draft: UnmanagedDraft
@State private var originalBody: String
@StateRealmObject var draft: Draft
@State private var editor = RichTextEditorModel()
@State private var showCc = false
@FocusState private var focusedField: ComposeViewFieldType?
Expand All @@ -64,62 +63,60 @@ struct ComposeMessageView: View {
@State private var isShowingFileSelection = false
@State private var isShowingPhotoLibrary = false

@State var sendDraft = false

@State var scrollView: UIScrollView?

@StateObject private var alert = NewMessageAlert()

@State private var sendDisabled: Bool

private var shouldDisplayAutocompletion: Bool {
return !autocompletion.isEmpty && focusedField != nil
}

private init(mailboxManager: MailboxManager, draft: UnmanagedDraft) {
private init(mailboxManager: MailboxManager, draft: Draft) {
_mailboxManager = State(initialValue: mailboxManager)
var initialDraft = draft
if initialDraft.identityId.isEmpty,
if draft.identityId == nil || draft.identityId?.isEmpty == true,
let signature = mailboxManager.getSignatureResponse() {
initialDraft.setSignature(signature)
draft.setSignature(signature)
}
_sendDisabled = State(initialValue: mailboxManager.getSignatureResponse() == nil || draft.to.isEmpty)
initialDraft.delay = UserDefaults.shared.cancelSendDelay.rawValue
_draft = State(initialValue: initialDraft)
_showCc = State(initialValue: !initialDraft.bcc.isEmpty || !initialDraft.cc.isEmpty)
_originalBody = State(initialValue: initialDraft.body)
let realm = mailboxManager.getRealm()
try? realm.write {
draft.action = draft.action == nil && draft.remoteUUID.isEmpty ? .initialSave : .save
draft.delay = UserDefaults.shared.cancelSendDelay.rawValue

realm.add(draft, update: .modified)
}

_draft = StateRealmObject(wrappedValue: draft)
_showCc = State(initialValue: !draft.bcc.isEmpty || !draft.cc.isEmpty)
}

static func newMessage(mailboxManager: MailboxManager) -> ComposeMessageView {
return ComposeMessageView(mailboxManager: mailboxManager, draft: .empty())
return ComposeMessageView(mailboxManager: mailboxManager, draft: Draft(localUUID: UUID().uuidString))
}

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 realm = mailboxManager.getRealm()
realm.refresh()
let freshMessage = message.fresh(using: realm) ?? message
let freshMessage = message.thaw() ?? message
return ComposeMessageView(
mailboxManager: mailboxManager,
draft: .replying(to: freshMessage, mode: messageReply.replyMode)
draft: .replying(to: freshMessage, mode: messageReply.replyMode, localDraftUUID: messageReply.localDraftUUID)
)
}

static func editDraft(draft: Draft, mailboxManager: MailboxManager) -> ComposeMessageView {
return ComposeMessageView(mailboxManager: mailboxManager, draft: draft.asUnmanaged())
return ComposeMessageView(mailboxManager: mailboxManager, draft: draft)
}

static func writingTo(recipient: Recipient, mailboxManager: MailboxManager) -> ComposeMessageView {
return ComposeMessageView(mailboxManager: mailboxManager, draft: .writing(to: recipient))
}

static func mailTo(urlComponents: URLComponents, mailboxManager: MailboxManager) -> ComposeMessageView {
let draft = UnmanagedDraft.mailTo(subject: urlComponents.getQueryItem(named: "subject"),
body: urlComponents.getQueryItem(named: "body"),
to: [Recipient(email: urlComponents.path, name: "")],
cc: Recipient.createListUsing(from: urlComponents, name: "cc"),
bcc: Recipient.createListUsing(from: urlComponents, name: "bcc"))
let draft = Draft.mailTo(subject: urlComponents.getQueryItem(named: "subject"),
body: urlComponents.getQueryItem(named: "body"),
to: [Recipient(email: urlComponents.path, name: "")],
cc: Recipient.createListUsing(from: urlComponents, name: "cc"),
bcc: Recipient.createListUsing(from: urlComponents, name: "bcc"))
return ComposeMessageView(mailboxManager: mailboxManager, draft: draft)
}

Expand Down Expand Up @@ -154,7 +151,8 @@ struct ComposeMessageView: View {
.focused($focusedField, equals: .subject)
}

if let attachments = draft.attachments?.filter { $0.contentId == nil }, !attachments.isEmpty {
if let attachments = draft.attachments.filter { $0.contentId == nil }.toArray(),
!attachments.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(attachments) { attachment in
Expand All @@ -179,6 +177,13 @@ struct ComposeMessageView: View {
}
}
}
.overlay {
if draft.messageUid != nil && draft.remoteUUID.isEmpty {
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(MailResourcesAsset.backgroundColor.swiftUiColor)
}
}
.introspectScrollView { scrollView in
self.scrollView = scrollView
}
Expand All @@ -198,43 +203,20 @@ struct ComposeMessageView: View {
Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark")
},
trailing: Button(action: {
sendDraft = true
originalBody = draft.body
Task {
await DraftManager.shared.instantSaveDraftLocally(draft: draft, mailboxManager: mailboxManager, action: .send)
dismiss()
}
sendDraft()
}, label: {
Image(resource: MailResourcesAsset.send)
})
.disabled(sendDisabled)
.disabled(draft.identityId?.isEmpty == true || draft.to.isEmpty)
)
.background(MailResourcesAsset.backgroundColor.swiftUiColor)
}
.onChange(of: draft) { _ in
Task {
await DraftManager.shared.saveDraftLocally(draft: draft, mailboxManager: mailboxManager, action: .save)
sendDisabled = draft.to.isEmpty
}
}
.onAppear {
focusedField = .to
}
.onDisappear {
// TODO: - Compare message body to original body : guard draft.body != originalBody || !draft.uuid.isEmpty else { return }

Task {
if !sendDraft {
await DraftManager.shared.instantSaveDraftLocally(
draft: draft,
mailboxManager: mailboxManager,
action: .save
)
}

DraftManager.shared.syncDraft(mailboxManager: mailboxManager)

sendDraft = false
}
}
.fullScreenCover(isPresented: $isShowingCamera) {
Expand Down Expand Up @@ -267,6 +249,18 @@ struct ComposeMessageView: View {
EmptyView()
}
}
.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
}
}
.navigationViewStyle(.stack)
.defaultAppStorage(.shared)
}
Expand All @@ -287,8 +281,8 @@ struct ComposeMessageView: View {
}
}

private func binding(for type: ComposeViewFieldType) -> Binding<[Recipient]> {
let binding: Binding<[Recipient]>
private func binding(for type: ComposeViewFieldType) -> Binding<RealmSwift.List<Recipient>> {
let binding: Binding<RealmSwift.List<Recipient>>
switch type {
case .to:
binding = $draft.to
Expand All @@ -297,11 +291,20 @@ struct ComposeMessageView: View {
case .bcc:
binding = $draft.bcc
default:
binding = .constant([])
fatalError("Unhandled binding \(type)")
}
return binding
}

private func sendDraft() {
if let liveDraft = draft.thaw() {
try? liveDraft.realm?.write {
liveDraft.action = .send
}
}
dismiss()
}

// MARK: Attachments

func addDocumentAttachment(urls: [URL]) async {
Expand Down Expand Up @@ -434,16 +437,12 @@ struct ComposeMessageView: View {
}

private func addAttachment(_ attachment: Attachment) {
if draft.attachments == nil {
draft.attachments = [attachment]
} else {
draft.attachments?.append(attachment)
}
draft.attachments.append(attachment)
}

private func removeAttachment(_ attachment: Attachment) {
if let attachments = draft.attachments, let attachmentToRemove = attachments.firstIndex(of: attachment) {
draft.attachments?.remove(at: attachmentToRemove)
if let attachmentToRemove = draft.attachments.firstIndex(of: attachment) {
draft.attachments.remove(at: attachmentToRemove)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Mail/Views/New Message/NewMessageCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import MailCore
import MailResources
import RealmSwift
import SwiftUI

extension VerticalAlignment {
Expand Down Expand Up @@ -80,7 +81,7 @@ struct RecipientCellView_Previews: PreviewProvider {
static var previews: some View {
NewMessageCell(type: .to,
showCc: .constant(false)) {
RecipientField(recipients: .constant([PreviewHelper.sampleRecipient1]),
RecipientField(recipients: .constant([PreviewHelper.sampleRecipient1].toRealmList()),
autocompletion: .constant([]),
addRecipientHandler: .constant { _ in /* Preview */ },
focusedField: .init(),
Expand Down
1 change: 0 additions & 1 deletion Mail/Views/Thread List/ThreadListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ class DateSection: Identifiable {
withAnimation {
isLoadingPage = false
}
await mailboxManager.draftOffline()
}

func updateThreads(with folder: Folder) async {
Expand Down
Loading

0 comments on commit 1dcf373

Please sign in to comment.