From 7d838888fae80fa892a460929dc437305281e3df Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Tue, 13 Jun 2023 14:14:51 +0200 Subject: [PATCH 01/41] feat: Create and present ComposeView v2 --- .../New Message/ComposeMessageViewV2.swift | 34 +++++++++++++++++++ Mail/Views/Thread List/ThreadListView.swift | 11 ++++++ 2 files changed, 45 insertions(+) create mode 100644 Mail/Views/New Message/ComposeMessageViewV2.swift diff --git a/Mail/Views/New Message/ComposeMessageViewV2.swift b/Mail/Views/New Message/ComposeMessageViewV2.swift new file mode 100644 index 000000000..8007b06ba --- /dev/null +++ b/Mail/Views/New Message/ComposeMessageViewV2.swift @@ -0,0 +1,34 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import SwiftUI + + +// TODO: Rename without V2 + +struct ComposeMessageViewV2: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct ComposeMessageViewV2_Previews: PreviewProvider { + static var previews: some View { + ComposeMessageViewV2() + } +} diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index ba207ce29..613bbae4f 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -44,6 +44,7 @@ struct ThreadListView: View { @AppStorage(UserDefaults.shared.key(.threadDensity)) private var threadDensity = DefaultPreferences.threadDensity @AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor + @State private var isShowingV2 = false @State private var isShowingComposeNewMessageView = false @State private var fetchingTask: Task? @State private var isRefreshing = false @@ -219,6 +220,11 @@ struct ThreadListView: View { matomo.track(eventWithCategory: .newMessage, name: "openFromFab") isShowingComposeNewMessageView.toggle() } + .overlay(alignment: .bottomLeading, content: { + MailButton(icon: MailResourcesAsset.pencil, label: "Show V2") { + isShowingV2 = true + } + }) .onAppear { networkMonitor.start() if viewModel.isCompact { @@ -247,6 +253,11 @@ struct ThreadListView: View { .sheet(isPresented: $isShowingComposeNewMessageView) { ComposeMessageView.newMessage(mailboxManager: viewModel.mailboxManager) } + .sheet(isPresented: $isShowingV2) { + NavigationView { + ComposeMessageViewV2() + } + } .customAlert(item: $flushAlert) { item in FlushFolderAlertView(flushAlert: item, folder: viewModel.folder) } From 6661563d4d1537905ff011673c1dbafdb9b53adb Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Tue, 13 Jun 2023 15:06:55 +0200 Subject: [PATCH 02/41] feat: Display toolbar with buttons and title --- .../New Message/ComposeMessageViewV2.swift | 34 ---------- .../New Message/V2/ComposeMessageViewV2.swift | 64 +++++++++++++++++++ Mail/Views/Thread List/ThreadListView.swift | 4 +- 3 files changed, 65 insertions(+), 37 deletions(-) delete mode 100644 Mail/Views/New Message/ComposeMessageViewV2.swift create mode 100644 Mail/Views/New Message/V2/ComposeMessageViewV2.swift diff --git a/Mail/Views/New Message/ComposeMessageViewV2.swift b/Mail/Views/New Message/ComposeMessageViewV2.swift deleted file mode 100644 index 8007b06ba..000000000 --- a/Mail/Views/New Message/ComposeMessageViewV2.swift +++ /dev/null @@ -1,34 +0,0 @@ -/* - Infomaniak Mail - iOS App - Copyright (C) 2022 Infomaniak Network SA - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -import SwiftUI - - -// TODO: Rename without V2 - -struct ComposeMessageViewV2: View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } -} - -struct ComposeMessageViewV2_Previews: PreviewProvider { - static var previews: some View { - ComposeMessageViewV2() - } -} diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift new file mode 100644 index 000000000..03beebe29 --- /dev/null +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -0,0 +1,64 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import MailResources +import SwiftUI + +// TODO: Rename without V2 + +struct ComposeMessageViewV2: View { + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationView { + ScrollView {} + .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: dismissDraft) { + Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark") + } + } + + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: sendDraft) { + Label(MailResourcesStrings.Localizable.buttonClose, image: "xmark") + Label(MailResourcesStrings.Localizable.send, image: MailResourcesAsset.send.name) + } + } + } + } + } + + private func dismissDraft() { + // TODO: Check attachments + dismiss() + } + + private func sendDraft() { + // TODO: Check attachments + dismiss() + } +} + +struct ComposeMessageViewV2_Previews: PreviewProvider { + static var previews: some View { + ComposeMessageViewV2() + } +} diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 613bbae4f..8e2e6b247 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -254,9 +254,7 @@ struct ThreadListView: View { ComposeMessageView.newMessage(mailboxManager: viewModel.mailboxManager) } .sheet(isPresented: $isShowingV2) { - NavigationView { - ComposeMessageViewV2() - } + ComposeMessageViewV2() } .customAlert(item: $flushAlert) { item in FlushFolderAlertView(flushAlert: item, folder: viewModel.folder) From 207e52402047a370124cddbcb6e270414c37009c Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Tue, 13 Jun 2023 16:11:40 +0200 Subject: [PATCH 03/41] feat: Divide in several files --- .../V2/ComposeMessageHeaderViewV2.swift | 31 ++++++++++++++++++ .../V2/ComposeMessageViewV2+Extension.swift | 23 +++++++++++++ .../New Message/V2/ComposeMessageViewV2.swift | 32 +++++++++++-------- 3 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift create mode 100644 Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift new file mode 100644 index 000000000..1ec1ed9d0 --- /dev/null +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -0,0 +1,31 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import SwiftUI + +struct ComposeMessageHeaderViewV2: View { + var body: some View { + Text("Liste des champs") + } +} + +struct ComposeMessageHeaderViewV2_Previews: PreviewProvider { + static var previews: some View { + ComposeMessageHeaderViewV2() + } +} diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift new file mode 100644 index 000000000..2be3df3f0 --- /dev/null +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift @@ -0,0 +1,23 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import Foundation + +extension ComposeMessageViewV2 { + // TODO: Init compose view here +} diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index 03beebe29..17d38d35a 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -16,33 +16,39 @@ along with this program. If not, see . */ +import MailCore import MailResources +import RealmSwift import SwiftUI // TODO: Rename without V2 struct ComposeMessageViewV2: View { @Environment(\.dismiss) private var dismiss + @EnvironmentObject private var mailboxManager: MailboxManager var body: some View { NavigationView { - ScrollView {} - .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button(action: dismissDraft) { - Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark") - } + ScrollView { + VStack(spacing: 0) { + ComposeMessageHeaderViewV2() + } + } + .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: dismissDraft) { + Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark") } + } - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: sendDraft) { - Label(MailResourcesStrings.Localizable.buttonClose, image: "xmark") - Label(MailResourcesStrings.Localizable.send, image: MailResourcesAsset.send.name) - } + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: sendDraft) { + Label(MailResourcesStrings.Localizable.send, image: MailResourcesAsset.send.name) } } + } } } From bf6813411a6d8ee5a7fe7d58745c5b5a51232923 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 09:12:31 +0200 Subject: [PATCH 04/41] feat: Create different cells --- Mail/Components/RecipientField.swift | 6 +-- .../V2/ComposeMessageCellTextFieldV2.swift | 41 +++++++++++++++ .../V2/ComposeMessageHeaderViewV2.swift | 23 ++++++++- .../ComposeMessageCellRecipientsV2.swift | 51 +++++++++++++++++++ .../ComposeMessageCellStaticTextV2.swift | 41 +++++++++++++++ 5 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 Mail/Views/New Message/V2/ComposeMessageCellTextFieldV2.swift create mode 100644 Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift create mode 100644 Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift diff --git a/Mail/Components/RecipientField.swift b/Mail/Components/RecipientField.swift index 11a746e36..06abdd0d3 100644 --- a/Mail/Components/RecipientField.swift +++ b/Mail/Components/RecipientField.swift @@ -26,6 +26,9 @@ import SwiftUI import WrappingHStack struct RecipientField: View { + @State private var currentText = "" + @State private var keyboardHeight: CGFloat = 0 + @Binding var recipients: RealmSwift.List @Binding var autocompletion: [Recipient] @Binding var unknownRecipientAutocompletion: String @@ -35,9 +38,6 @@ struct RecipientField: View { let type: ComposeViewFieldType - @State private var currentText = "" - @State private var keyboardHeight: CGFloat = 0 - /// A trimmed view on `currentText` private var trimmedInputText: String { currentText.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/Mail/Views/New Message/V2/ComposeMessageCellTextFieldV2.swift b/Mail/Views/New Message/V2/ComposeMessageCellTextFieldV2.swift new file mode 100644 index 000000000..4737342e3 --- /dev/null +++ b/Mail/Views/New Message/V2/ComposeMessageCellTextFieldV2.swift @@ -0,0 +1,41 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import SwiftUI + +struct ComposeMessageCellTextFieldV2: View { + @Binding var text: String + + let type: ComposeViewFieldType + + var body: some View { + HStack { + Text(type.title) + .textStyle(.bodySecondary) + + TextField("", text: $text) + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +struct ComposeMessageCellTextFieldV2_Previews: PreviewProvider { + static var previews: some View { + ComposeMessageCellTextFieldV2(text: .constant(""), type: .subject) + } +} diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift index 1ec1ed9d0..b2e1b2294 100644 --- a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -16,11 +16,32 @@ along with this program. If not, see . */ +import MailCore import SwiftUI struct ComposeMessageHeaderViewV2: View { + @EnvironmentObject private var mailboxManager: MailboxManager + var body: some View { - Text("Liste des champs") + VStack { + ComposeMessageCellStaticTextV2(type: .from, text: mailboxManager.mailbox.email) + IKDivider() + ComposeMessageCellRecipientsV2(recipients: .constant([ + PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 + ].toRealmList()), type: .to) + IKDivider() + ComposeMessageCellRecipientsV2(recipients: .constant([ + PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 + ].toRealmList()), type: .cc) + IKDivider() + ComposeMessageCellRecipientsV2(recipients: .constant([ + PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 + ].toRealmList()), type: .bcc) + IKDivider() + ComposeMessageCellTextFieldV2(text: .constant(""), type: .subject) + IKDivider() + } + .padding(.horizontal, 16) } } diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift new file mode 100644 index 000000000..535eaf45e --- /dev/null +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -0,0 +1,51 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import MailCore +import RealmSwift +import SwiftUI + +struct ComposeMessageCellRecipientsV2: View { + @Binding var recipients: RealmSwift.List + + let type: ComposeViewFieldType + + var body: some View { + HStack { + Text(type.title) + .textStyle(.bodySecondary) + + RecipientField( + recipients: $recipients, + autocompletion: .constant([]), + unknownRecipientAutocompletion: .constant(""), + addRecipientHandler: .constant(nil), + type: type + ) + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +struct ComposeMessageCellRecipientsV2_Previews: PreviewProvider { + static var previews: some View { + ComposeMessageCellRecipientsV2(recipients: .constant([ + PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 + ].toRealmList()), type: .to) + } +} diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift new file mode 100644 index 000000000..9726dbab6 --- /dev/null +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift @@ -0,0 +1,41 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import SwiftUI + +struct ComposeMessageCellStaticTextV2: View { + let type: ComposeViewFieldType + let text: String + + var body: some View { + HStack { + Text(type.title) + .textStyle(.bodySecondary) + + Text(text) + .textStyle(.body) + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +struct ComposeMessageStaticTextV2_Previews: PreviewProvider { + static var previews: some View { + ComposeMessageCellStaticTextV2(type: .from, text: "myaddress@email.com") + } +} From 8f001fd27c988b8b9884691a6f25bf08a6b952a8 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 10:08:55 +0200 Subject: [PATCH 05/41] feat: Fold cells --- .../V2/ComposeMessageHeaderViewV2.swift | 25 ++++++++++++------- .../ComposeMessageCellRecipientsV2.swift | 8 +++++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift index b2e1b2294..39a28a239 100644 --- a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -22,22 +22,29 @@ import SwiftUI struct ComposeMessageHeaderViewV2: View { @EnvironmentObject private var mailboxManager: MailboxManager + @State private var showAllRecipientsFields = false + var body: some View { VStack { ComposeMessageCellStaticTextV2(type: .from, text: mailboxManager.mailbox.email) IKDivider() + ComposeMessageCellRecipientsV2(recipients: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), type: .to) - IKDivider() - ComposeMessageCellRecipientsV2(recipients: .constant([ - PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), type: .cc) - IKDivider() - ComposeMessageCellRecipientsV2(recipients: .constant([ - PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), type: .bcc) + ].toRealmList()), showAllRecipientsFields: $showAllRecipientsFields, type: .to) IKDivider() + + if showAllRecipientsFields { + ComposeMessageCellRecipientsV2(recipients: .constant([ + PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 + ].toRealmList()), showAllRecipientsFields: $showAllRecipientsFields, type: .cc) + IKDivider() + ComposeMessageCellRecipientsV2(recipients: .constant([ + PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 + ].toRealmList()), showAllRecipientsFields: $showAllRecipientsFields, type: .bcc) + IKDivider() + } + ComposeMessageCellTextFieldV2(text: .constant(""), type: .subject) IKDivider() } diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index 535eaf45e..fb6b10710 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -22,6 +22,7 @@ import SwiftUI struct ComposeMessageCellRecipientsV2: View { @Binding var recipients: RealmSwift.List + @Binding var showAllRecipientsFields: Bool let type: ComposeViewFieldType @@ -37,6 +38,11 @@ struct ComposeMessageCellRecipientsV2: View { addRecipientHandler: .constant(nil), type: type ) + + if type == .to { + Spacer() + ChevronButton(isExpanded: $showAllRecipientsFields) + } } .frame(maxWidth: .infinity, alignment: .leading) } @@ -46,6 +52,6 @@ struct ComposeMessageCellRecipientsV2_Previews: PreviewProvider { static var previews: some View { ComposeMessageCellRecipientsV2(recipients: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), type: .to) + ].toRealmList()), showAllRecipientsFields: .constant(false), type: .to) } } From 09643acaae4ff7c55a7d2fc1eb62e9029f348cd1 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 10:50:03 +0200 Subject: [PATCH 06/41] feat: Create view with draft --- .../ComposeMessageCellStaticTextV2.swift | 0 .../V2/ComposeMessageHeaderViewV2.swift | 23 ++++++++----------- .../V2/ComposeMessageViewV2+Extension.swift | 20 +++++++++++++++- .../New Message/V2/ComposeMessageViewV2.swift | 6 +++-- .../ComposeMessageBodyViewV2.swift | 20 ++++++++++++++++ .../ComposeMessageCellRecipientsV2.swift | 6 ++--- Mail/Views/Thread List/ThreadListView.swift | 2 +- 7 files changed, 57 insertions(+), 20 deletions(-) rename Mail/Views/New Message/V2/{Header Cells => }/ComposeMessageCellStaticTextV2.swift (100%) create mode 100644 Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift b/Mail/Views/New Message/V2/ComposeMessageCellStaticTextV2.swift similarity index 100% rename from Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift rename to Mail/Views/New Message/V2/ComposeMessageCellStaticTextV2.swift diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift index 39a28a239..e843f79e8 100644 --- a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -18,34 +18,31 @@ import MailCore import SwiftUI +import RealmSwift struct ComposeMessageHeaderViewV2: View { @EnvironmentObject private var mailboxManager: MailboxManager - @State private var showAllRecipientsFields = false + @State private var showRecipientsFields = false + + @StateRealmObject var draft: Draft var body: some View { VStack { ComposeMessageCellStaticTextV2(type: .from, text: mailboxManager.mailbox.email) IKDivider() - ComposeMessageCellRecipientsV2(recipients: .constant([ - PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), showAllRecipientsFields: $showAllRecipientsFields, type: .to) + ComposeMessageCellRecipientsV2(recipients: $draft.to, showRecipientsFields: $showRecipientsFields, type: .to) IKDivider() - if showAllRecipientsFields { - ComposeMessageCellRecipientsV2(recipients: .constant([ - PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), showAllRecipientsFields: $showAllRecipientsFields, type: .cc) + if showRecipientsFields { + ComposeMessageCellRecipientsV2(recipients: $draft.cc, showRecipientsFields: $showRecipientsFields, type: .cc) IKDivider() - ComposeMessageCellRecipientsV2(recipients: .constant([ - PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), showAllRecipientsFields: $showAllRecipientsFields, type: .bcc) + ComposeMessageCellRecipientsV2(recipients: $draft.bcc, showRecipientsFields: $showRecipientsFields, type: .bcc) IKDivider() } - ComposeMessageCellTextFieldV2(text: .constant(""), type: .subject) + ComposeMessageCellTextFieldV2(text: $draft.subject, type: .subject) IKDivider() } .padding(.horizontal, 16) @@ -54,6 +51,6 @@ struct ComposeMessageHeaderViewV2: View { struct ComposeMessageHeaderViewV2_Previews: PreviewProvider { static var previews: some View { - ComposeMessageHeaderViewV2() + ComposeMessageHeaderViewV2(draft: Draft()) } } diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift index 2be3df3f0..d1d01126f 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift @@ -17,7 +17,25 @@ */ import Foundation +import MailCore +import RealmSwift extension ComposeMessageViewV2 { - // TODO: Init compose view here + static func newMessage(mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + let draft = Draft(localUUID: UUID().uuidString) + saveDraftInRealm(mailboxManager.getRealm(), draft: draft) + + return ComposeMessageViewV2(draft: draft) + } +} + +extension ComposeMessageViewV2 { + private static func saveDraftInRealm(_ realm: Realm, draft: Draft) { + try? realm.write { + draft.action = draft.action == nil && draft.remoteUUID.isEmpty ? .initialSave : .save + draft.delay = UserDefaults.shared.cancelSendDelay.rawValue + + realm.add(draft, update: .modified) + } + } } diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index 17d38d35a..a070453ed 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -27,11 +27,13 @@ struct ComposeMessageViewV2: View { @Environment(\.dismiss) private var dismiss @EnvironmentObject private var mailboxManager: MailboxManager + @StateRealmObject var draft: Draft + var body: some View { NavigationView { ScrollView { VStack(spacing: 0) { - ComposeMessageHeaderViewV2() + ComposeMessageHeaderViewV2(draft: draft) } } .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) @@ -65,6 +67,6 @@ struct ComposeMessageViewV2: View { struct ComposeMessageViewV2_Previews: PreviewProvider { static var previews: some View { - ComposeMessageViewV2() + ComposeMessageViewV2.newMessage(mailboxManager: PreviewHelper.sampleMailboxManager) } } diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift new file mode 100644 index 000000000..1344e4811 --- /dev/null +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift @@ -0,0 +1,20 @@ +// +// ComposeMessageBodyViewV2.swift +// +// +// Created by Valentin Perignon on 14/06/2023. +// + +import SwiftUI + +struct ComposeMessageBodyViewV2: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct ComposeMessageBodyViewV2_Previews: PreviewProvider { + static var previews: some View { + ComposeMessageBodyViewV2() + } +} diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index fb6b10710..23a3ed84f 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -22,7 +22,7 @@ import SwiftUI struct ComposeMessageCellRecipientsV2: View { @Binding var recipients: RealmSwift.List - @Binding var showAllRecipientsFields: Bool + @Binding var showRecipientsFields: Bool let type: ComposeViewFieldType @@ -41,7 +41,7 @@ struct ComposeMessageCellRecipientsV2: View { if type == .to { Spacer() - ChevronButton(isExpanded: $showAllRecipientsFields) + ChevronButton(isExpanded: $showRecipientsFields) } } .frame(maxWidth: .infinity, alignment: .leading) @@ -52,6 +52,6 @@ struct ComposeMessageCellRecipientsV2_Previews: PreviewProvider { static var previews: some View { ComposeMessageCellRecipientsV2(recipients: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), showAllRecipientsFields: .constant(false), type: .to) + ].toRealmList()), showRecipientsFields: .constant(false), type: .to) } } diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 8e2e6b247..66c777669 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -254,7 +254,7 @@ struct ThreadListView: View { ComposeMessageView.newMessage(mailboxManager: viewModel.mailboxManager) } .sheet(isPresented: $isShowingV2) { - ComposeMessageViewV2() + ComposeMessageViewV2.newMessage(mailboxManager: viewModel.mailboxManager) } .customAlert(item: $flushAlert) { item in FlushFolderAlertView(flushAlert: item, folder: viewModel.folder) From a6640a217de8d44afde84db730bfbed3831acf70 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 11:23:52 +0200 Subject: [PATCH 07/41] feat: Create body and init --- .../V2/ComposeMessageViewV2+Extension.swift | 15 +-------- .../New Message/V2/ComposeMessageViewV2.swift | 25 ++++++++++++-- .../ComposeMessageBodyViewV2.swift | 33 ++++++++++++++----- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift index d1d01126f..d20126256 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift @@ -23,19 +23,6 @@ import RealmSwift extension ComposeMessageViewV2 { static func newMessage(mailboxManager: MailboxManager) -> ComposeMessageViewV2 { let draft = Draft(localUUID: UUID().uuidString) - saveDraftInRealm(mailboxManager.getRealm(), draft: draft) - - return ComposeMessageViewV2(draft: draft) - } -} - -extension ComposeMessageViewV2 { - private static func saveDraftInRealm(_ realm: Realm, draft: Draft) { - try? realm.write { - draft.action = draft.action == nil && draft.remoteUUID.isEmpty ? .initialSave : .save - draft.delay = UserDefaults.shared.cancelSendDelay.rawValue - - realm.add(draft, update: .modified) - } + return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) } } diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index a070453ed..0fe641fb8 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -25,15 +25,27 @@ import SwiftUI struct ComposeMessageViewV2: View { @Environment(\.dismiss) private var dismiss - @EnvironmentObject private var mailboxManager: MailboxManager - @StateRealmObject var draft: Draft + @StateObject private var mailboxManager: MailboxManager + @StateObject private var attachmentsManager: AttachmentsManager + + @StateRealmObject private var draft: Draft + + init(draft: Draft, mailboxManager: MailboxManager) { + Self.saveNewDraftInRealm(mailboxManager.getRealm(), draft: draft) + _draft = StateRealmObject(wrappedValue: draft) + + _mailboxManager = StateObject(wrappedValue: mailboxManager) + _attachmentsManager = StateObject(wrappedValue: AttachmentsManager(draft: draft, mailboxManager: mailboxManager)) + } var body: some View { NavigationView { ScrollView { VStack(spacing: 0) { ComposeMessageHeaderViewV2(draft: draft) + + ComposeMessageBodyViewV2(attachmentsManager: attachmentsManager) } } .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) @@ -63,6 +75,15 @@ struct ComposeMessageViewV2: View { // TODO: Check attachments dismiss() } + + private static func saveNewDraftInRealm(_ realm: Realm, draft: Draft) { + try? realm.write { + draft.action = draft.action == nil && draft.remoteUUID.isEmpty ? .initialSave : .save + draft.delay = UserDefaults.shared.cancelSendDelay.rawValue + + realm.add(draft, update: .modified) + } + } } struct ComposeMessageViewV2_Previews: PreviewProvider { diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift index 1344e4811..a765de786 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift @@ -1,20 +1,37 @@ -// -// ComposeMessageBodyViewV2.swift -// -// -// Created by Valentin Perignon on 14/06/2023. -// +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import MailCore import SwiftUI struct ComposeMessageBodyViewV2: View { + @ObservedObject var attachmentsManager: AttachmentsManager + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + VStack { + AttachmentsHeaderView(attachmentsManager: attachmentsManager) + } } } struct ComposeMessageBodyViewV2_Previews: PreviewProvider { static var previews: some View { - ComposeMessageBodyViewV2() + ComposeMessageBodyViewV2(attachmentsManager: AttachmentsManager(draft: Draft(), + mailboxManager: PreviewHelper.sampleMailboxManager)) } } From 461a0f795157954cc714e42b6d2c10ff7490670d Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 11:42:39 +0200 Subject: [PATCH 08/41] feat: Add message editor --- Mail/Helpers/RichTextEditor.swift | 12 ++++----- .../New Message/ComposeMessageView.swift | 4 +-- .../New Message/V2/ComposeMessageViewV2.swift | 3 ++- .../ComposeMessageBodyViewV2.swift | 27 +++++++++++++++++-- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Mail/Helpers/RichTextEditor.swift b/Mail/Helpers/RichTextEditor.swift index 337dffb70..6cc23662d 100644 --- a/Mail/Helpers/RichTextEditor.swift +++ b/Mail/Helpers/RichTextEditor.swift @@ -28,7 +28,7 @@ import WebKit struct RichTextEditor: UIViewRepresentable { typealias UIViewType = MailEditorView - @Binding var model: RichTextEditorModel + @ObservedObject var model: RichTextEditorModel @Binding var body: String @Binding var isShowingCamera: Bool @Binding var isShowingFileSelection: Bool @@ -37,12 +37,12 @@ struct RichTextEditor: UIViewRepresentable { let blockRemoteContent: Bool var alert: ObservedObject.Wrapper - init(model: Binding, body: Binding, + init(model: RichTextEditorModel, body: Binding, alert: ObservedObject.Wrapper, isShowingCamera: Binding, isShowingFileSelection: Binding, isShowingPhotoLibrary: Binding, becomeFirstResponder: Binding, blockRemoteContent: Bool) { - _model = model + _model = ObservedObject(wrappedValue: model) _body = body self.alert = alert _isShowingCamera = isShowingCamera @@ -146,9 +146,9 @@ extension SQTextEditorView { } } -struct RichTextEditorModel { - var cursorPosition: CGFloat = 0 - var height: CGFloat = 0 +class RichTextEditorModel: ObservableObject { + @Published var cursorPosition: CGFloat = 0 + @Published var height: CGFloat = 0 } class MailEditorView: SQTextEditorView { diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift index fedfc295b..abe456846 100644 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -66,7 +66,7 @@ struct ComposeMessageView: View { @State private var mailboxManager: MailboxManager @StateRealmObject var draft: Draft - @State private var editor = RichTextEditorModel() + @StateObject private var editor = RichTextEditorModel() @State private var showCc = false @State private var isLoadingContent: Bool @FocusState private var focusedField: ComposeViewFieldType? { @@ -160,7 +160,7 @@ struct ComposeMessageView: View { AttachmentsHeaderView(attachmentsManager: attachmentsManager) - RichTextEditor(model: $editor, + RichTextEditor(model: editor, body: $draft.body, alert: $alert, isShowingCamera: $isShowingCamera, diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index 0fe641fb8..99a95ab9d 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -28,6 +28,7 @@ struct ComposeMessageViewV2: View { @StateObject private var mailboxManager: MailboxManager @StateObject private var attachmentsManager: AttachmentsManager + @StateObject private var alert = NewMessageAlert() @StateRealmObject private var draft: Draft @@ -45,7 +46,7 @@ struct ComposeMessageViewV2: View { VStack(spacing: 0) { ComposeMessageHeaderViewV2(draft: draft) - ComposeMessageBodyViewV2(attachmentsManager: attachmentsManager) + ComposeMessageBodyViewV2(attachmentsManager: attachmentsManager, alert: alert, draft: draft) } } .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift index a765de786..5b761ac9a 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift @@ -17,21 +17,44 @@ */ import MailCore +import RealmSwift import SwiftUI struct ComposeMessageBodyViewV2: View { + @State private var isShowingCamera = false + @State private var isShowingFileSelection = false + @State private var isShowingPhotoLibrary = false + + @StateObject private var editorModel = RichTextEditorModel() + @ObservedObject var attachmentsManager: AttachmentsManager + @ObservedObject var alert: NewMessageAlert + @ObservedRealmObject var draft: Draft var body: some View { VStack { AttachmentsHeaderView(attachmentsManager: attachmentsManager) + + RichTextEditor( + model: editorModel, + body: $draft.body, + alert: $alert, + isShowingCamera: $isShowingCamera, + isShowingFileSelection: $isShowingFileSelection, + isShowingPhotoLibrary: $isShowingPhotoLibrary, + becomeFirstResponder: .constant(false), // TODO: Give real value + blockRemoteContent: false // TODO: Give real value + ) } } } struct ComposeMessageBodyViewV2_Previews: PreviewProvider { static var previews: some View { - ComposeMessageBodyViewV2(attachmentsManager: AttachmentsManager(draft: Draft(), - mailboxManager: PreviewHelper.sampleMailboxManager)) + ComposeMessageBodyViewV2( + attachmentsManager: AttachmentsManager(draft: Draft(), mailboxManager: PreviewHelper.sampleMailboxManager), + alert: NewMessageAlert(), + draft: Draft() + ) } } From 0544c20f6298a9bb4712c15b3dde487064d70ce0 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 11:48:01 +0200 Subject: [PATCH 09/41] feat: Import attachments --- .../ComposeMessageBodyViewV2.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift index 5b761ac9a..9f56d3478 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift @@ -45,6 +45,27 @@ struct ComposeMessageBodyViewV2: View { becomeFirstResponder: .constant(false), // TODO: Give real value blockRemoteContent: false // TODO: Give real value ) + .ignoresSafeArea(.all, edges: .bottom) + .frame(height: editorModel.height + 20) + .padding([.vertical], 10) + } + .fullScreenCover(isPresented: $isShowingCamera) { + CameraPicker { data in + attachmentsManager.importAttachments(attachments: [data]) + } + .ignoresSafeArea() + } + .sheet(isPresented: $isShowingFileSelection) { + DocumentPicker(pickerType: .selectContent([.item]) { urls in + attachmentsManager.importAttachments(attachments: urls) + }) + .ignoresSafeArea() + } + .sheet(isPresented: $isShowingPhotoLibrary) { + ImagePicker { results in + attachmentsManager.importAttachments(attachments: results) + } + .ignoresSafeArea() } } } From de6d6e08f31d032c173114beb8be4c6783f37aa1 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 13:37:34 +0200 Subject: [PATCH 10/41] feat: Move some computed properties to V2 --- .../New Message/ComposeMessageView.swift | 31 ----------- .../V2/ComposeMessageHeaderViewV2.swift | 3 ++ .../New Message/V2/ComposeMessageViewV2.swift | 51 ++++++++++++++++++- .../ComposeMessageBodyViewV2.swift | 13 +++-- MailCore/Models/Draft.swift | 4 ++ 5 files changed, 66 insertions(+), 36 deletions(-) diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift index abe456846..1c99199bb 100644 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -27,37 +27,6 @@ import RealmSwift import Sentry import SwiftUI -enum ComposeViewFieldType: Hashable { - case from, to, cc, bcc, subject, editor - case chip(Int, Recipient) - - var title: String { - switch self { - case .from: - return MailResourcesStrings.Localizable.fromTitle - case .to: - return MailResourcesStrings.Localizable.toTitle - case .cc: - return MailResourcesStrings.Localizable.ccTitle - case .bcc: - return MailResourcesStrings.Localizable.bccTitle - case .subject: - return MailResourcesStrings.Localizable.subjectTitle - case .editor: - return "editor" - case .chip: - return "Recipient Chip" - } - } -} - -final class NewMessageAlert: SheetState { - enum State { - case link(handler: (String) -> Void) - case emptySubject(handler: () -> Void) - } -} - struct ComposeMessageView: View { @Environment(\.dismiss) private var dismiss diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift index e843f79e8..6e071d9e1 100644 --- a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -46,6 +46,9 @@ struct ComposeMessageHeaderViewV2: View { IKDivider() } .padding(.horizontal, 16) + .onAppear { + showRecipientsFields = !draft.bcc.isEmpty || !draft.cc.isEmpty + } } } diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index 99a95ab9d..f8cdfbe6d 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -23,6 +23,37 @@ import SwiftUI // TODO: Rename without V2 +enum ComposeViewFieldType: Hashable { + case from, to, cc, bcc, subject, editor + case chip(Int, Recipient) + + var title: String { + switch self { + case .from: + return MailResourcesStrings.Localizable.fromTitle + case .to: + return MailResourcesStrings.Localizable.toTitle + case .cc: + return MailResourcesStrings.Localizable.ccTitle + case .bcc: + return MailResourcesStrings.Localizable.bccTitle + case .subject: + return MailResourcesStrings.Localizable.subjectTitle + case .editor: + return "editor" + case .chip: + return "Recipient Chip" + } + } +} + +final class NewMessageAlert: SheetState { + enum State { + case link(handler: (String) -> Void) + case emptySubject(handler: () -> Void) + } +} + struct ComposeMessageViewV2: View { @Environment(\.dismiss) private var dismiss @@ -32,7 +63,15 @@ struct ComposeMessageViewV2: View { @StateRealmObject private var draft: Draft - init(draft: Draft, mailboxManager: MailboxManager) { + let messageReply: MessageReply? + + private var isSendButtonDisabled: Bool { + return draft.identityId?.isEmpty == true || draft.recipientsAreEmpty || !attachmentsManager.allAttachmentsUploaded + } + + init(draft: Draft, mailboxManager: MailboxManager, messageReply: MessageReply? = nil) { + self.messageReply = messageReply + Self.saveNewDraftInRealm(mailboxManager.getRealm(), draft: draft) _draft = StateRealmObject(wrappedValue: draft) @@ -46,9 +85,15 @@ struct ComposeMessageViewV2: View { VStack(spacing: 0) { ComposeMessageHeaderViewV2(draft: draft) - ComposeMessageBodyViewV2(attachmentsManager: attachmentsManager, alert: alert, draft: draft) + ComposeMessageBodyViewV2( + attachmentsManager: attachmentsManager, + alert: alert, + draft: draft, + messageReply: messageReply + ) } } + .background(MailResourcesAsset.backgroundColor.swiftUIColor) .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -61,10 +106,12 @@ struct ComposeMessageViewV2: View { ToolbarItem(placement: .navigationBarTrailing) { Button(action: sendDraft) { Label(MailResourcesStrings.Localizable.send, image: MailResourcesAsset.send.name) + .disabled(isSendButtonDisabled) } } } } + .matomoView(view: ["ComposeMessage"]) } private func dismissDraft() { diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift index 9f56d3478..c040e3b53 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift @@ -31,6 +31,12 @@ struct ComposeMessageBodyViewV2: View { @ObservedObject var alert: NewMessageAlert @ObservedRealmObject var draft: Draft + let messageReply: MessageReply? + + private var isRemoteContentBlocked: Bool { + return UserDefaults.shared.displayExternalContent == .askMe && messageReply?.message.localSafeDisplay == false + } + var body: some View { VStack { AttachmentsHeaderView(attachmentsManager: attachmentsManager) @@ -43,11 +49,11 @@ struct ComposeMessageBodyViewV2: View { isShowingFileSelection: $isShowingFileSelection, isShowingPhotoLibrary: $isShowingPhotoLibrary, becomeFirstResponder: .constant(false), // TODO: Give real value - blockRemoteContent: false // TODO: Give real value + blockRemoteContent: isRemoteContentBlocked ) .ignoresSafeArea(.all, edges: .bottom) .frame(height: editorModel.height + 20) - .padding([.vertical], 10) + .padding([.vertical], 8) } .fullScreenCover(isPresented: $isShowingCamera) { CameraPicker { data in @@ -75,7 +81,8 @@ struct ComposeMessageBodyViewV2_Previews: PreviewProvider { ComposeMessageBodyViewV2( attachmentsManager: AttachmentsManager(draft: Draft(), mailboxManager: PreviewHelper.sampleMailboxManager), alert: NewMessageAlert(), - draft: Draft() + draft: Draft(), + messageReply: nil ) } } diff --git a/MailCore/Models/Draft.swift b/MailCore/Models/Draft.swift index 475b6a44d..704628484 100644 --- a/MailCore/Models/Draft.swift +++ b/MailCore/Models/Draft.swift @@ -95,6 +95,10 @@ public class Draft: Object, Codable, Identifiable { /// Store compressed data to reduce realm size. @Persisted var bodyData: Data? + public var recipientsAreEmpty: Bool { + to.isEmpty && cc.isEmpty && bcc.isEmpty + } + private enum CodingKeys: String, CodingKey { case remoteUUID = "uuid" case date From 56c25e3b33eba7b27caa4def6adde0684ea47771 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 14:16:24 +0200 Subject: [PATCH 11/41] feat: Send or dismiss draft --- .../ComposeMessageBodyViewV2.swift | 0 .../New Message/V2/ComposeMessageViewV2.swift | 50 ++++++++++++++++--- .../ComposeMessageCellStaticTextV2.swift | 0 .../ComposeMessageCellTextFieldV2.swift | 0 4 files changed, 44 insertions(+), 6 deletions(-) rename Mail/Views/New Message/V2/{Header Cells => }/ComposeMessageBodyViewV2.swift (100%) rename Mail/Views/New Message/V2/{ => Header Cells}/ComposeMessageCellStaticTextV2.swift (100%) rename Mail/Views/New Message/V2/{ => Header Cells}/ComposeMessageCellTextFieldV2.swift (100%) diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift similarity index 100% rename from Mail/Views/New Message/V2/Header Cells/ComposeMessageBodyViewV2.swift rename to Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index f8cdfbe6d..b253a03e8 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -16,6 +16,8 @@ along with this program. If not, see . */ +import InfomaniakCoreUI +import InfomaniakDI import MailCore import MailResources import RealmSwift @@ -57,6 +59,10 @@ final class NewMessageAlert: SheetState { struct ComposeMessageViewV2: View { @Environment(\.dismiss) private var dismiss + @LazyInjectService private var matomo: MatomoUtils + + @State private var isShowingCancelAttachmentsError = false + @StateObject private var mailboxManager: MailboxManager @StateObject private var attachmentsManager: AttachmentsManager @StateObject private var alert = NewMessageAlert() @@ -98,29 +104,61 @@ struct ComposeMessageViewV2: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - Button(action: dismissDraft) { + Button(action: didTouchDismiss) { Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark") } } ToolbarItem(placement: .navigationBarTrailing) { - Button(action: sendDraft) { + Button(action: didTouchSend) { Label(MailResourcesStrings.Localizable.send, image: MailResourcesAsset.send.name) .disabled(isSendButtonDisabled) } } } + .customAlert(isPresented: $alert.isShowing) { + switch alert.state { + case let .link(handler): + AddLinkView(actionHandler: handler) + case let .emptySubject(handler): + EmptySubjectView(actionHandler: handler) + case .none: + EmptyView() + } + } + .customAlert(isPresented: $isShowingCancelAttachmentsError) { + AttachmentsUploadInProgressErrorView { + dismiss() + } + } + .matomoView(view: ["ComposeMessage"]) } - .matomoView(view: ["ComposeMessage"]) } - private func dismissDraft() { - // TODO: Check attachments + private func didTouchDismiss() { + guard attachmentsManager.allAttachmentsUploaded else { + isShowingCancelAttachmentsError = true + return + } dismiss() } + private func didTouchSend() { + guard !draft.subject.isEmpty else { + matomo.track(eventWithCategory: .newMessage, name: "sendWithoutSubject") + alert.state = .emptySubject(handler: sendDraft) + return + } + sendDraft() + } + private func sendDraft() { - // TODO: Check attachments + matomo.trackSendMessage(numberOfTo: draft.to.count, numberOfCc: draft.cc.count, numberOfBcc: draft.bcc.count) + if let liveDraft = draft.thaw() { + try? liveDraft.realm?.write { + liveDraft.action = .send + } + } dismiss() } diff --git a/Mail/Views/New Message/V2/ComposeMessageCellStaticTextV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift similarity index 100% rename from Mail/Views/New Message/V2/ComposeMessageCellStaticTextV2.swift rename to Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift diff --git a/Mail/Views/New Message/V2/ComposeMessageCellTextFieldV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift similarity index 100% rename from Mail/Views/New Message/V2/ComposeMessageCellTextFieldV2.swift rename to Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift From c4a38dd723d2e5c691d3409e0fbcb1341f905f0a Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 14:40:17 +0200 Subject: [PATCH 12/41] feat: Prepare body --- .../V2/ComposeMessageBodyViewV2.swift | 93 ++++++++++++++++++- .../New Message/V2/ComposeMessageViewV2.swift | 8 +- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift index c040e3b53..81715de29 100644 --- a/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift @@ -16,20 +16,27 @@ along with this program. If not, see . */ +import InfomaniakCoreUI import MailCore import RealmSwift import SwiftUI struct ComposeMessageBodyViewV2: View { + @Environment(\.dismiss) private var dismiss + @EnvironmentObject private var mailboxManager: MailboxManager + @State private var isShowingCamera = false @State private var isShowingFileSelection = false @State private var isShowingPhotoLibrary = false @StateObject private var editorModel = RichTextEditorModel() + @StateRealmObject var draft: Draft + + @Binding var isLoadingContent: Bool + @ObservedObject var attachmentsManager: AttachmentsManager @ObservedObject var alert: NewMessageAlert - @ObservedRealmObject var draft: Draft let messageReply: MessageReply? @@ -55,6 +62,13 @@ struct ComposeMessageBodyViewV2: View { .frame(height: editorModel.height + 20) .padding([.vertical], 8) } + .task { + await prepareCompleteDraft() + } + .task { + await prepareReplyForwardBodyAndAttachments() + await setSignature() + } .fullScreenCover(isPresented: $isShowingCamera) { CameraPicker { data in attachmentsManager.importAttachments(attachments: [data]) @@ -74,14 +88,89 @@ struct ComposeMessageBodyViewV2: View { .ignoresSafeArea() } } + + 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 prepareTask = Task.detached { + try await prepareBody(message: messageReply.message, replyMode: messageReply.replyMode) + try await prepareAttachments(message: messageReply.message, replyMode: messageReply.replyMode) + } + + do { + _ = try await prepareTask.value + + isLoadingContent = false + } catch { + dismiss() + IKSnackBar.showSnackBar(message: MailError.unknownError.localizedDescription) + } + } + + private func setSignature() async { + if draft.identityId == nil || draft.identityId?.isEmpty == true, + let signatureResponse = mailboxManager.getSignatureResponse() { + $draft.identityId.wrappedValue = "\(signatureResponse.defaultSignatureId)" + guard let signature = signatureResponse.default else { + return + } + + let html = "

\(signature.content)
" + switch signature.position { + case .beforeReplyMessage: + $draft.body.wrappedValue.insert(contentsOf: html, at: draft.body.startIndex) + case .afterReplyMessage: + $draft.body.wrappedValue.append(contentsOf: html) + } + } + } + + 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() + } } struct ComposeMessageBodyViewV2_Previews: PreviewProvider { static var previews: some View { ComposeMessageBodyViewV2( + draft: Draft(), + isLoadingContent: .constant(false), attachmentsManager: AttachmentsManager(draft: Draft(), mailboxManager: PreviewHelper.sampleMailboxManager), alert: NewMessageAlert(), - draft: Draft(), messageReply: nil ) } diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index b253a03e8..e3f836f0d 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -61,6 +61,7 @@ struct ComposeMessageViewV2: View { @LazyInjectService private var matomo: MatomoUtils + @State private var isLoadingContent: Bool @State private var isShowingCancelAttachmentsError = false @StateObject private var mailboxManager: MailboxManager @@ -81,6 +82,8 @@ struct ComposeMessageViewV2: View { Self.saveNewDraftInRealm(mailboxManager.getRealm(), draft: draft) _draft = StateRealmObject(wrappedValue: draft) + _isLoadingContent = State(wrappedValue: (draft.messageUid != nil && draft.remoteUUID.isEmpty) || messageReply != nil) + _mailboxManager = StateObject(wrappedValue: mailboxManager) _attachmentsManager = StateObject(wrappedValue: AttachmentsManager(draft: draft, mailboxManager: mailboxManager)) } @@ -92,9 +95,10 @@ struct ComposeMessageViewV2: View { ComposeMessageHeaderViewV2(draft: draft) ComposeMessageBodyViewV2( + draft: draft, + isLoadingContent: $isLoadingContent, attachmentsManager: attachmentsManager, alert: alert, - draft: draft, messageReply: messageReply ) } @@ -112,8 +116,8 @@ struct ComposeMessageViewV2: View { ToolbarItem(placement: .navigationBarTrailing) { Button(action: didTouchSend) { Label(MailResourcesStrings.Localizable.send, image: MailResourcesAsset.send.name) - .disabled(isSendButtonDisabled) } + .disabled(isSendButtonDisabled) } } .customAlert(isPresented: $alert.isShowing) { From eb5865d7fc0248c833a0a3310f21c197cc2abc6e Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Wed, 14 Jun 2023 15:59:13 +0200 Subject: [PATCH 13/41] feat: Sync draft on disappear --- Mail/Views/New Message/V2/ComposeMessageViewV2.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index e3f836f0d..9162fdcb1 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -104,6 +104,11 @@ struct ComposeMessageViewV2: View { } } .background(MailResourcesAsset.backgroundColor.swiftUIColor) + .onDisappear { + Task { + DraftManager.shared.syncDraft(mailboxManager: mailboxManager) + } + } .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) .navigationBarTitleDisplayMode(.inline) .toolbar { From 48439a6675d6909734a9630cada213a1ff94c279 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Thu, 15 Jun 2023 10:03:30 +0200 Subject: [PATCH 14/41] fix: Correct UITextField size --- Mail/Views/New Message/RecipientsTextField.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mail/Views/New Message/RecipientsTextField.swift b/Mail/Views/New Message/RecipientsTextField.swift index b32f52f02..4f9c71653 100644 --- a/Mail/Views/New Message/RecipientsTextField.swift +++ b/Mail/Views/New Message/RecipientsTextField.swift @@ -29,6 +29,8 @@ struct RecipientsTextFieldView: UIViewRepresentable { let textField = RecipientsTextField() textField.delegate = context.coordinator textField.addTarget(context.coordinator, action: #selector(context.coordinator.textDidChanged(_:)), for: .editingChanged) + textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + textField.setContentHuggingPriority(.defaultHigh, for: .vertical) textField.onBackspace = onBackspace return textField } From f8862e18fddc9c08fda5622df55a77f250d6d251 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Thu, 15 Jun 2023 13:54:18 +0200 Subject: [PATCH 15/41] refactor: Create new views and handle focus --- .../V2/ComposeMessageHeaderViewV2.swift | 26 +++-- .../New Message/V2/ComposeMessageViewV2.swift | 23 +++- .../ComposeMessageCellRecipientsV2.swift | 33 +++--- .../New Message/V2/RecipientFieldV2.swift | 106 ++++++++++++++++++ .../V2/RecipientsTextFieldV2.swift | 68 +++++++++++ 5 files changed, 230 insertions(+), 26 deletions(-) create mode 100644 Mail/Views/New Message/V2/RecipientFieldV2.swift create mode 100644 Mail/Views/New Message/V2/RecipientsTextFieldV2.swift diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift index 6e071d9e1..b354b5ce6 100644 --- a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -17,8 +17,8 @@ */ import MailCore -import SwiftUI import RealmSwift +import SwiftUI struct ComposeMessageHeaderViewV2: View { @EnvironmentObject private var mailboxManager: MailboxManager @@ -27,19 +27,31 @@ struct ComposeMessageHeaderViewV2: View { @StateRealmObject var draft: Draft + @FocusState var focusedField: ComposeViewFieldType? + var body: some View { VStack { ComposeMessageCellStaticTextV2(type: .from, text: mailboxManager.mailbox.email) IKDivider() - ComposeMessageCellRecipientsV2(recipients: $draft.to, showRecipientsFields: $showRecipientsFields, type: .to) - IKDivider() + ComposeMessageCellRecipientsV2( + recipients: $draft.to, + showRecipientsFields: $showRecipientsFields, + type: .to + ) if showRecipientsFields { - ComposeMessageCellRecipientsV2(recipients: $draft.cc, showRecipientsFields: $showRecipientsFields, type: .cc) - IKDivider() - ComposeMessageCellRecipientsV2(recipients: $draft.bcc, showRecipientsFields: $showRecipientsFields, type: .bcc) - IKDivider() + ComposeMessageCellRecipientsV2( + recipients: $draft.cc, + showRecipientsFields: $showRecipientsFields, + type: .cc + ) + + ComposeMessageCellRecipientsV2( + recipients: $draft.bcc, + showRecipientsFields: $showRecipientsFields, + type: .bcc + ) } ComposeMessageCellTextFieldV2(text: $draft.subject, type: .subject) diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index 9162fdcb1..1b18f370f 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -26,7 +26,7 @@ import SwiftUI // TODO: Rename without V2 enum ComposeViewFieldType: Hashable { - case from, to, cc, bcc, subject, editor + case from, to, cc, bcc, subject, editor, autocomplete case chip(Int, Recipient) var title: String { @@ -45,6 +45,8 @@ enum ComposeViewFieldType: Hashable { return "editor" case .chip: return "Recipient Chip" + case .autocomplete: + return "autocomplete" } } } @@ -64,12 +66,21 @@ struct ComposeMessageViewV2: View { @State private var isLoadingContent: Bool @State private var isShowingCancelAttachmentsError = false + @State private var editorFocus = false + @StateObject private var mailboxManager: MailboxManager @StateObject private var attachmentsManager: AttachmentsManager @StateObject private var alert = NewMessageAlert() @StateRealmObject private var draft: Draft + @FocusState private var focusedField: ComposeViewFieldType? { + willSet { + let editorInFocus = (newValue == .editor) + editorFocus = editorInFocus + } + } + let messageReply: MessageReply? private var isSendButtonDisabled: Bool { @@ -92,7 +103,7 @@ struct ComposeMessageViewV2: View { NavigationView { ScrollView { VStack(spacing: 0) { - ComposeMessageHeaderViewV2(draft: draft) + ComposeMessageHeaderViewV2(draft: draft, focusedField: _focusedField) ComposeMessageBodyViewV2( draft: draft, @@ -104,6 +115,14 @@ struct ComposeMessageViewV2: View { } } .background(MailResourcesAsset.backgroundColor.swiftUIColor) + .onAppear { + switch messageReply?.replyMode { + case .reply, .replyAll: + focusedField = .editor + default: + focusedField = .to + } + } .onDisappear { Task { DraftManager.shared.syncDraft(mailboxManager: mailboxManager) diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index 23a3ed84f..a36aaafcd 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -21,30 +21,29 @@ import RealmSwift import SwiftUI struct ComposeMessageCellRecipientsV2: View { + @State private var currentText = "" + @Binding var recipients: RealmSwift.List @Binding var showRecipientsFields: Bool let type: ComposeViewFieldType var body: some View { - HStack { - Text(type.title) - .textStyle(.bodySecondary) - - RecipientField( - recipients: $recipients, - autocompletion: .constant([]), - unknownRecipientAutocompletion: .constant(""), - addRecipientHandler: .constant(nil), - type: type - ) - - if type == .to { - Spacer() - ChevronButton(isExpanded: $showRecipientsFields) + VStack { + HStack { + Text(type.title) + .textStyle(.bodySecondary) + + RecipientFieldV2(currentText: $currentText, recipients: $recipients, type: type) + + if type == .to { + Spacer() + ChevronButton(isExpanded: $showRecipientsFields) + } } + .frame(maxWidth: .infinity, alignment: .leading) + IKDivider() } - .frame(maxWidth: .infinity, alignment: .leading) } } @@ -52,6 +51,6 @@ struct ComposeMessageCellRecipientsV2_Previews: PreviewProvider { static var previews: some View { ComposeMessageCellRecipientsV2(recipients: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), showRecipientsFields: .constant(false), type: .to) + ].toRealmList()), showRecipientsFields: .constant(false), type: .bcc) } } diff --git a/Mail/Views/New Message/V2/RecipientFieldV2.swift b/Mail/Views/New Message/V2/RecipientFieldV2.swift new file mode 100644 index 000000000..3630513af --- /dev/null +++ b/Mail/Views/New Message/V2/RecipientFieldV2.swift @@ -0,0 +1,106 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import InfomaniakCore +import InfomaniakCoreUI +import InfomaniakDI +import MailCore +import MailResources +import RealmSwift +import SwiftUI +import WrappingHStack + +struct RecipientFieldV2: View { + @State private var keyboardHeight: CGFloat = 0 + + @Binding var currentText: String + @Binding var recipients: RealmSwift.List + + @FocusState var focusedField: ComposeViewFieldType? + + let type: ComposeViewFieldType + var onSubmit: (() -> Void)? + + /// A trimmed view on `currentText` + private var trimmedInputText: String { + currentText.trimmingCharacters(in: .whitespacesAndNewlines) + } + + var body: some View { + VStack { + if !recipients.isEmpty { + WrappingHStack(recipients.indices, spacing: .constant(8), lineSpacing: 8) { i in + RecipientChip(recipient: recipients[i], fieldType: type, focusedField: _focusedField) { + remove(recipientAt: i) + } switchFocusHandler: { + switchFocus() + } + .focused($focusedField, equals: .chip(type.hashValue, recipients[i])) + } + .alignmentGuide(.newMessageCellAlignment) { d in d[.top] + 21 } + } + + RecipientsTextFieldV2View(text: $currentText, onSubmit: onSubmit, onBackspace: handleBackspaceTextField) + .focused($focusedField, equals: type) + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { output in + if let userInfo = output.userInfo, + let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { + keyboardHeight = keyboardFrame.height + } + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification)) { _ in + keyboardHeight = 0 + } + } + + @MainActor private func remove(recipientAt: Int) { + withAnimation { + $recipients.remove(at: recipientAt) + } + } + + private func handleBackspaceTextField(isTextEmpty: Bool) { + if let recipient = recipients.last, isTextEmpty { + focusedField = .chip(type.hashValue, recipient) + } + } + + private func switchFocus() { + guard case .chip(let hash, let recipient) = focusedField else { return } + + if recipient == recipients.last { + focusedField = type + } else if let recipientIndex = recipients.firstIndex(of: recipient) { + focusedField = .chip(hash, recipients[recipientIndex + 1]) + } + } +} + +struct RecipientFieldV2_Previews: PreviewProvider { + static var previews: some View { + RecipientField(recipients: .constant([ + PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 + ].toRealmList()), + autocompletion: .constant([]), + unknownRecipientAutocompletion: .constant(""), + addRecipientHandler: .constant { _ in /* Preview */ }, + focusedField: .init(), + type: .to) + } +} diff --git a/Mail/Views/New Message/V2/RecipientsTextFieldV2.swift b/Mail/Views/New Message/V2/RecipientsTextFieldV2.swift new file mode 100644 index 000000000..19151c10f --- /dev/null +++ b/Mail/Views/New Message/V2/RecipientsTextFieldV2.swift @@ -0,0 +1,68 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import SwiftUI +import UIKit + +struct RecipientsTextFieldV2View: UIViewRepresentable { + @Binding var text: String + + var onSubmit: (() -> Void)? + let onBackspace: (Bool) -> Void + + func makeUIView(context: Context) -> UITextField { + let textField = RecipientsTextField() + textField.delegate = context.coordinator + textField.addTarget(context.coordinator, action: #selector(context.coordinator.textDidChanged(_:)), for: .editingChanged) + textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + textField.setContentHuggingPriority(.defaultHigh, for: .vertical) + textField.onBackspace = onBackspace + return textField + } + + func updateUIView(_ textField: UITextField, context: Context) { + guard textField.text != text else { return } + textField.text = text + } + + func makeCoordinator() -> Coordinator { + return Coordinator(self) + } + + class Coordinator: NSObject, UITextFieldDelegate { + let parent: RecipientsTextFieldV2View + + init(_ parent: RecipientsTextFieldV2View) { + self.parent = parent + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + guard textField.text?.isEmpty == false else { + textField.resignFirstResponder() + return true + } + + parent.onSubmit?() + return true + } + + @objc func textDidChanged(_ textField: UITextField) { + parent.text = textField.text ?? "" + } + } +} From 14852fee89c24979942c4febb8f42cbc770d6f1c Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Thu, 15 Jun 2023 15:19:03 +0200 Subject: [PATCH 16/41] feat: Handle autocompletion visibilty --- .../V2/ComposeMessageHeaderViewV2.swift | 17 +++++++--- .../New Message/V2/ComposeMessageViewV2.swift | 25 +++++++------- .../ComposeMessageCellRecipientsV2.swift | 34 +++++++++++++------ .../ComposeMessageCellStaticTextV2.swift | 23 ++++++++----- .../ComposeMessageCellTextFieldV2.swift | 20 +++++++---- 5 files changed, 77 insertions(+), 42 deletions(-) diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift index b354b5ce6..89ad3d4f2 100644 --- a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -29,14 +29,20 @@ struct ComposeMessageHeaderViewV2: View { @FocusState var focusedField: ComposeViewFieldType? + @Binding var autocompletionType: ComposeViewFieldType? + var body: some View { VStack { - ComposeMessageCellStaticTextV2(type: .from, text: mailboxManager.mailbox.email) - IKDivider() + ComposeMessageCellStaticTextV2( + autocompletionType: $autocompletionType, + type: .from, + text: mailboxManager.mailbox.email + ) ComposeMessageCellRecipientsV2( recipients: $draft.to, showRecipientsFields: $showRecipientsFields, + autocompletionType: $autocompletionType, type: .to ) @@ -44,18 +50,19 @@ struct ComposeMessageHeaderViewV2: View { ComposeMessageCellRecipientsV2( recipients: $draft.cc, showRecipientsFields: $showRecipientsFields, + autocompletionType: $autocompletionType, type: .cc ) ComposeMessageCellRecipientsV2( recipients: $draft.bcc, showRecipientsFields: $showRecipientsFields, + autocompletionType: $autocompletionType, type: .bcc ) } - ComposeMessageCellTextFieldV2(text: $draft.subject, type: .subject) - IKDivider() + ComposeMessageCellTextFieldV2(text: $draft.subject, autocompletionType: $autocompletionType, type: .subject) } .padding(.horizontal, 16) .onAppear { @@ -66,6 +73,6 @@ struct ComposeMessageHeaderViewV2: View { struct ComposeMessageHeaderViewV2_Previews: PreviewProvider { static var previews: some View { - ComposeMessageHeaderViewV2(draft: Draft()) + ComposeMessageHeaderViewV2(draft: Draft(), autocompletionType: .constant(nil)) } } diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index 1b18f370f..db7aedfe0 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -23,8 +23,6 @@ import MailResources import RealmSwift import SwiftUI -// TODO: Rename without V2 - enum ComposeViewFieldType: Hashable { case from, to, cc, bcc, subject, editor, autocomplete case chip(Int, Recipient) @@ -65,7 +63,8 @@ struct ComposeMessageViewV2: View { @State private var isLoadingContent: Bool @State private var isShowingCancelAttachmentsError = false - + @State private var autocompletionField: ComposeViewFieldType? + @State private var autocompletionType: ComposeViewFieldType? @State private var editorFocus = false @StateObject private var mailboxManager: MailboxManager @@ -103,15 +102,17 @@ struct ComposeMessageViewV2: View { NavigationView { ScrollView { VStack(spacing: 0) { - ComposeMessageHeaderViewV2(draft: draft, focusedField: _focusedField) - - ComposeMessageBodyViewV2( - draft: draft, - isLoadingContent: $isLoadingContent, - attachmentsManager: attachmentsManager, - alert: alert, - messageReply: messageReply - ) + ComposeMessageHeaderViewV2(draft: draft, focusedField: _focusedField, autocompletionType: $autocompletionType) + + if autocompletionType == nil { + ComposeMessageBodyViewV2( + draft: draft, + isLoadingContent: $isLoadingContent, + attachmentsManager: attachmentsManager, + alert: alert, + messageReply: messageReply + ) + } } } .background(MailResourcesAsset.backgroundColor.swiftUIColor) diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index a36aaafcd..44a47fc8d 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -25,24 +25,38 @@ struct ComposeMessageCellRecipientsV2: View { @Binding var recipients: RealmSwift.List @Binding var showRecipientsFields: Bool + @Binding var autocompletionType: ComposeViewFieldType? let type: ComposeViewFieldType var body: some View { VStack { - HStack { - Text(type.title) - .textStyle(.bodySecondary) + if autocompletionType == nil || autocompletionType == type { + HStack { + Text(type.title) + .textStyle(.bodySecondary) - RecipientFieldV2(currentText: $currentText, recipients: $recipients, type: type) + RecipientFieldV2(currentText: $currentText, recipients: $recipients, type: type) - if type == .to { - Spacer() - ChevronButton(isExpanded: $showRecipientsFields) + if type == .to && autocompletionType == nil { + Spacer() + ChevronButton(isExpanded: $showRecipientsFields) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + IKDivider() + } + + // TODO: Add autocomplete + } + .onChange(of: currentText) { newValue in + withAnimation { + if newValue.isEmpty { + autocompletionType = nil + } else { + autocompletionType = type } } - .frame(maxWidth: .infinity, alignment: .leading) - IKDivider() } } } @@ -51,6 +65,6 @@ struct ComposeMessageCellRecipientsV2_Previews: PreviewProvider { static var previews: some View { ComposeMessageCellRecipientsV2(recipients: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), showRecipientsFields: .constant(false), type: .bcc) + ].toRealmList()), showRecipientsFields: .constant(false), autocompletionType: .constant(nil), type: .bcc) } } diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift index 9726dbab6..bb8fe3ab1 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift @@ -19,23 +19,30 @@ import SwiftUI struct ComposeMessageCellStaticTextV2: View { + @Binding var autocompletionType: ComposeViewFieldType? + let type: ComposeViewFieldType let text: String var body: some View { - HStack { - Text(type.title) - .textStyle(.bodySecondary) - - Text(text) - .textStyle(.body) + if autocompletionType == nil { + VStack { + HStack { + Text(type.title) + .textStyle(.bodySecondary) + + Text(text) + .textStyle(.body) + } + .frame(maxWidth: .infinity, alignment: .leading) + IKDivider() + } } - .frame(maxWidth: .infinity, alignment: .leading) } } struct ComposeMessageStaticTextV2_Previews: PreviewProvider { static var previews: some View { - ComposeMessageCellStaticTextV2(type: .from, text: "myaddress@email.com") + ComposeMessageCellStaticTextV2(autocompletionType: .constant(nil), type: .from, text: "myaddress@email.com") } } diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift index 4737342e3..e17f55b8e 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift @@ -20,22 +20,28 @@ import SwiftUI struct ComposeMessageCellTextFieldV2: View { @Binding var text: String + @Binding var autocompletionType: ComposeViewFieldType? let type: ComposeViewFieldType var body: some View { - HStack { - Text(type.title) - .textStyle(.bodySecondary) - - TextField("", text: $text) + if autocompletionType == nil { + VStack { + HStack { + Text(type.title) + .textStyle(.bodySecondary) + + TextField("", text: $text) + } + .frame(maxWidth: .infinity, alignment: .leading) + IKDivider() + } } - .frame(maxWidth: .infinity, alignment: .leading) } } struct ComposeMessageCellTextFieldV2_Previews: PreviewProvider { static var previews: some View { - ComposeMessageCellTextFieldV2(text: .constant(""), type: .subject) + ComposeMessageCellTextFieldV2(text: .constant(""), autocompletionType: .constant(nil), type: .subject) } } From ac5b554836a13ae785967bd1474668c02d850ed6 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Thu, 15 Jun 2023 16:31:03 +0200 Subject: [PATCH 17/41] feat: Update autocompletion --- .../New Message/V2/AutocompletionViewV2.swift | 75 +++++++++++++++++++ .../ComposeMessageCellRecipientsV2.swift | 5 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 Mail/Views/New Message/V2/AutocompletionViewV2.swift diff --git a/Mail/Views/New Message/V2/AutocompletionViewV2.swift b/Mail/Views/New Message/V2/AutocompletionViewV2.swift new file mode 100644 index 000000000..c26700f0b --- /dev/null +++ b/Mail/Views/New Message/V2/AutocompletionViewV2.swift @@ -0,0 +1,75 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import MailCore +import RealmSwift +import SwiftUI + +struct AutocompletionViewV2: View { + @State private var recipients = [Recipient]() + + @Binding var currentSearch: String + @Binding var addedRecipients: RealmSwift.List + + var body: some View { + LazyVStack { + ForEach(recipients) { recipient in + VStack(alignment: .leading, spacing: 8) { + Button { + addedRecipients.append(recipient) + } label: { + RecipientCell(recipient: recipient) + } + .padding(.horizontal, 8) + + IKDivider() + } + } + } + .onChange(of: currentSearch, perform: updateAutocompletion) + } + + private func updateAutocompletion(_ search: String) { + let trimmedSearch = search.trimmingCharacters(in: .whitespacesAndNewlines) + + print("coucou") + + guard let contactManager = AccountManager.instance.currentContactManager else { + withAnimation { + recipients = [] + } + return + } + + let autocompleteContacts = contactManager.contacts(matching: trimmedSearch) + let autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) } + + withAnimation { + recipients = autocompleteRecipients + } + } +} + +struct AutocompletionViewV2_Previews: PreviewProvider { + static var previews: some View { + AutocompletionViewV2( + currentSearch: .constant(""), + addedRecipients: .constant([PreviewHelper.sampleRecipient1].toRealmList()) + ) + } +} diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index 44a47fc8d..d2d04e9eb 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -47,8 +47,11 @@ struct ComposeMessageCellRecipientsV2: View { IKDivider() } - // TODO: Add autocomplete + if autocompletionType == type { + AutocompletionViewV2(currentSearch: $currentText, addedRecipients: $recipients) + } } + .border(.red) .onChange(of: currentText) { newValue in withAnimation { if newValue.isEmpty { From 5138a8a0757a2c40802bbfebe557397955d20ec5 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 16 Jun 2023 08:14:09 +0200 Subject: [PATCH 18/41] fix: Update views properties --- .../New Message/V2/AutocompletionViewV2.swift | 26 +++++++++++++++---- .../V2/ComposeMessageHeaderViewV2.swift | 2 +- .../ComposeMessageCellRecipientsV2.swift | 1 - 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Mail/Views/New Message/V2/AutocompletionViewV2.swift b/Mail/Views/New Message/V2/AutocompletionViewV2.swift index c26700f0b..285aad5a6 100644 --- a/Mail/Views/New Message/V2/AutocompletionViewV2.swift +++ b/Mail/Views/New Message/V2/AutocompletionViewV2.swift @@ -16,7 +16,10 @@ along with this program. If not, see . */ +import InfomaniakCoreUI +import InfomaniakDI import MailCore +import MailResources import RealmSwift import SwiftUI @@ -31,7 +34,7 @@ struct AutocompletionViewV2: View { ForEach(recipients) { recipient in VStack(alignment: .leading, spacing: 8) { Button { - addedRecipients.append(recipient) + addNewRecipient(recipient) } label: { RecipientCell(recipient: recipient) } @@ -44,17 +47,30 @@ struct AutocompletionViewV2: View { .onChange(of: currentSearch, perform: updateAutocompletion) } - private func updateAutocompletion(_ search: String) { - let trimmedSearch = search.trimmingCharacters(in: .whitespacesAndNewlines) + @MainActor private func addNewRecipient(_ recipient: Recipient) { + @InjectService var matomo: MatomoUtils + matomo.track(eventWithCategory: .newMessage, name: "addNewRecipient") - print("coucou") + if Constants.isEmailAddress(recipient.email) { + withAnimation { + addedRecipients.append(recipient) + } + currentSearch = "" + } else { + IKSnackBar + .showSnackBar(message: MailResourcesStrings.Localizable.addUnknownRecipientInvalidEmail) + } + } + private func updateAutocompletion(_ search: String) { guard let contactManager = AccountManager.instance.currentContactManager else { withAnimation { recipients = [] } return - } + } + + let trimmedSearch = search.trimmingCharacters(in: .whitespacesAndNewlines) let autocompleteContacts = contactManager.contacts(matching: trimmedSearch) let autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) } diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift index 89ad3d4f2..544911878 100644 --- a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -25,7 +25,7 @@ struct ComposeMessageHeaderViewV2: View { @State private var showRecipientsFields = false - @StateRealmObject var draft: Draft + @ObservedRealmObject var draft: Draft @FocusState var focusedField: ComposeViewFieldType? diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index d2d04e9eb..a38643244 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -51,7 +51,6 @@ struct ComposeMessageCellRecipientsV2: View { AutocompletionViewV2(currentSearch: $currentText, addedRecipients: $recipients) } } - .border(.red) .onChange(of: currentText) { newValue in withAnimation { if newValue.isEmpty { From f0e9f2365ece8c8628ed497995c803720cd97529 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 16 Jun 2023 09:11:04 +0200 Subject: [PATCH 19/41] feat: Add custom recipient --- .../New Message/V2/AutocompletionViewV2.swift | 41 +++++++------------ .../ComposeMessageCellRecipientsV2.swift | 32 ++++++++++++++- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/Mail/Views/New Message/V2/AutocompletionViewV2.swift b/Mail/Views/New Message/V2/AutocompletionViewV2.swift index 285aad5a6..9201a5c38 100644 --- a/Mail/Views/New Message/V2/AutocompletionViewV2.swift +++ b/Mail/Views/New Message/V2/AutocompletionViewV2.swift @@ -16,25 +16,23 @@ along with this program. If not, see . */ -import InfomaniakCoreUI -import InfomaniakDI import MailCore -import MailResources import RealmSwift import SwiftUI struct AutocompletionViewV2: View { - @State private var recipients = [Recipient]() - + @Binding var autocompletion: [Recipient] @Binding var currentSearch: String @Binding var addedRecipients: RealmSwift.List + let addRecipient: @MainActor (Recipient) -> Void + var body: some View { LazyVStack { - ForEach(recipients) { recipient in + ForEach(autocompletion) { recipient in VStack(alignment: .leading, spacing: 8) { Button { - addNewRecipient(recipient) + addRecipient(recipient) } label: { RecipientCell(recipient: recipient) } @@ -47,25 +45,10 @@ struct AutocompletionViewV2: View { .onChange(of: currentSearch, perform: updateAutocompletion) } - @MainActor private func addNewRecipient(_ recipient: Recipient) { - @InjectService var matomo: MatomoUtils - matomo.track(eventWithCategory: .newMessage, name: "addNewRecipient") - - if Constants.isEmailAddress(recipient.email) { - withAnimation { - addedRecipients.append(recipient) - } - currentSearch = "" - } else { - IKSnackBar - .showSnackBar(message: MailResourcesStrings.Localizable.addUnknownRecipientInvalidEmail) - } - } - private func updateAutocompletion(_ search: String) { guard let contactManager = AccountManager.instance.currentContactManager else { withAnimation { - recipients = [] + autocompletion = [] } return } @@ -73,10 +56,15 @@ struct AutocompletionViewV2: View { let trimmedSearch = search.trimmingCharacters(in: .whitespacesAndNewlines) let autocompleteContacts = contactManager.contacts(matching: trimmedSearch) - let autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) } + var autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) } + + let realResults = autocompleteRecipients.filter { !addedRecipients.map(\.email).contains($0.email) } + if !currentSearch.isEmpty && realResults.isEmpty { + autocompleteRecipients.append(Recipient(email: currentSearch, name: "")) + } withAnimation { - recipients = autocompleteRecipients + autocompletion = autocompleteRecipients } } } @@ -84,8 +72,9 @@ struct AutocompletionViewV2: View { struct AutocompletionViewV2_Previews: PreviewProvider { static var previews: some View { AutocompletionViewV2( + autocompletion: .constant([]), currentSearch: .constant(""), addedRecipients: .constant([PreviewHelper.sampleRecipient1].toRealmList()) - ) + ) { _ in /* Preview */ } } } diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index a38643244..e331b72f5 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -16,12 +16,16 @@ along with this program. If not, see . */ +import InfomaniakCoreUI +import InfomaniakDI import MailCore +import MailResources import RealmSwift import SwiftUI struct ComposeMessageCellRecipientsV2: View { @State private var currentText = "" + @State private var autocompletion = [Recipient]() @Binding var recipients: RealmSwift.List @Binding var showRecipientsFields: Bool @@ -36,7 +40,11 @@ struct ComposeMessageCellRecipientsV2: View { Text(type.title) .textStyle(.bodySecondary) - RecipientFieldV2(currentText: $currentText, recipients: $recipients, type: type) + RecipientFieldV2(currentText: $currentText, recipients: $recipients, type: type) { + if let bestMatch = autocompletion.first { + addNewRecipient(bestMatch) + } + } if type == .to && autocompletionType == nil { Spacer() @@ -48,7 +56,12 @@ struct ComposeMessageCellRecipientsV2: View { } if autocompletionType == type { - AutocompletionViewV2(currentSearch: $currentText, addedRecipients: $recipients) + AutocompletionViewV2( + autocompletion: $autocompletion, + currentSearch: $currentText, + addedRecipients: $recipients, + addRecipient: addNewRecipient + ) } } .onChange(of: currentText) { newValue in @@ -61,6 +74,21 @@ struct ComposeMessageCellRecipientsV2: View { } } } + + @MainActor private func addNewRecipient(_ recipient: Recipient) { + @InjectService var matomo: MatomoUtils + matomo.track(eventWithCategory: .newMessage, name: "addNewRecipient") + + if Constants.isEmailAddress(recipient.email) { + withAnimation { + recipients.append(recipient) + } + currentText = "" + } else { + IKSnackBar + .showSnackBar(message: MailResourcesStrings.Localizable.addUnknownRecipientInvalidEmail) + } + } } struct ComposeMessageCellRecipientsV2_Previews: PreviewProvider { From 5ff2a590e8c5c9c1ac1994eef513f1389891bb82 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 16 Jun 2023 10:07:32 +0200 Subject: [PATCH 20/41] feat: Highlight recipient string --- Mail/Components/RecipientCell.swift | 29 +++++++++-- .../New Message/V2/AutocompletionCell.swift | 50 +++++++++++++++++++ .../New Message/V2/AutocompletionViewV2.swift | 27 ++++++---- 3 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 Mail/Views/New Message/V2/AutocompletionCell.swift diff --git a/Mail/Components/RecipientCell.swift b/Mail/Components/RecipientCell.swift index fb637b42c..cd43298c2 100644 --- a/Mail/Components/RecipientCell.swift +++ b/Mail/Components/RecipientCell.swift @@ -21,6 +21,19 @@ import SwiftUI struct RecipientCell: View { let recipient: Recipient + var highlight: String? + + var name: AttributedString { + let data = AttributedString(recipient.name) + + return data + } + + var message: AttributedString { + let data = AttributedString(recipient.name) + + return data + } var body: some View { HStack(spacing: 8) { @@ -28,13 +41,13 @@ struct RecipientCell: View { .accessibilityHidden(true) if recipient.name.isEmpty { - Text(recipient.email) + Text(highlightedAttributedString(from: recipient.email)) .textStyle(.bodyMedium) } else { VStack(alignment: .leading) { - Text(recipient.name) + Text(highlightedAttributedString(from: recipient.name)) .textStyle(.bodyMedium) - Text(recipient.email) + Text(highlightedAttributedString(from: recipient.email)) .textStyle(.bodySecondary) } } @@ -44,6 +57,16 @@ struct RecipientCell: View { .accessibilityElement(children: .combine) .accessibilityAddTraits(.isButton) } + + private func highlightedAttributedString(from data: String) -> AttributedString { + var attributedString = AttributedString(data) + guard let highlight else { return attributedString } + + if let range = attributedString.range(of: highlight, options: .caseInsensitive) { + attributedString[range].foregroundColor = .accentColor + } + return attributedString + } } struct RecipientAutocompletionCell_Previews: PreviewProvider { diff --git a/Mail/Views/New Message/V2/AutocompletionCell.swift b/Mail/Views/New Message/V2/AutocompletionCell.swift new file mode 100644 index 000000000..25f3fe166 --- /dev/null +++ b/Mail/Views/New Message/V2/AutocompletionCell.swift @@ -0,0 +1,50 @@ +// +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import SwiftUI +import MailCore + +struct AutocompletionCell: View { + let addRecipient: @MainActor (Recipient) -> Void + let recipient: Recipient + var highlight: String? + let alreadyAppend: Bool + + var body: some View { + HStack { + Button { + addRecipient(recipient) + } label: { + RecipientCell(recipient: recipient, highlight: highlight) + } + .disabled(alreadyAppend) + + if alreadyAppend { + Text("v") // TODO: Realm image + } + } + .padding(.horizontal, 8) + } +} + +struct AutocompletionCell_Previews: PreviewProvider { + static var previews: some View { + AutocompletionCell(addRecipient: { _ in /* Preview */}, recipient: PreviewHelper.sampleRecipient1, alreadyAppend: false) + } +} diff --git a/Mail/Views/New Message/V2/AutocompletionViewV2.swift b/Mail/Views/New Message/V2/AutocompletionViewV2.swift index 9201a5c38..c06e51dc7 100644 --- a/Mail/Views/New Message/V2/AutocompletionViewV2.swift +++ b/Mail/Views/New Message/V2/AutocompletionViewV2.swift @@ -21,6 +21,8 @@ import RealmSwift import SwiftUI struct AutocompletionViewV2: View { + @State private var hasNoResult = false + @Binding var autocompletion: [Recipient] @Binding var currentSearch: String @Binding var addedRecipients: RealmSwift.List @@ -31,16 +33,23 @@ struct AutocompletionViewV2: View { LazyVStack { ForEach(autocompletion) { recipient in VStack(alignment: .leading, spacing: 8) { - Button { - addRecipient(recipient) - } label: { - RecipientCell(recipient: recipient) - } - .padding(.horizontal, 8) - + AutocompletionCell( + addRecipient: addRecipient, + recipient: recipient, + highlight: currentSearch, + alreadyAppend: addedRecipients.contains(recipient) + ) IKDivider() } } + + if hasNoResult { + AutocompletionCell( + addRecipient: addRecipient, + recipient: Recipient(email: currentSearch, name: ""), + alreadyAppend: false + ) + } } .onChange(of: currentSearch, perform: updateAutocompletion) } @@ -59,12 +68,10 @@ struct AutocompletionViewV2: View { var autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) } let realResults = autocompleteRecipients.filter { !addedRecipients.map(\.email).contains($0.email) } - if !currentSearch.isEmpty && realResults.isEmpty { - autocompleteRecipients.append(Recipient(email: currentSearch, name: "")) - } withAnimation { autocompletion = autocompleteRecipients + hasNoResult = !currentSearch.isEmpty && realResults.isEmpty } } } From b7a59e14e20297b77085eaa5bd05105c6f5aabcc Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 16 Jun 2023 10:26:12 +0200 Subject: [PATCH 21/41] feat: Create all ComposeView inits --- Mail/SceneDelegate.swift | 2 +- .../Bottom sheets/ContactActionsView.swift | 2 +- .../V2/ComposeMessageViewV2+Extension.swift | 23 +++++++++++++++++++ Mail/Views/SplitView.swift | 2 +- Mail/Views/Thread/MessageHeaderView.swift | 2 +- Mail/Views/ThreadListManagerView.swift | 2 +- MailCore/Models/Draft.swift | 13 +++++++++++ 7 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Mail/SceneDelegate.swift b/Mail/SceneDelegate.swift index 110d2ef8e..4b3cae583 100644 --- a/Mail/SceneDelegate.swift +++ b/Mail/SceneDelegate.swift @@ -184,7 +184,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AccountManagerDelegate } if Constants.isMailTo(url) { - let newMessageView = ComposeMessageView.mailTo(urlComponents: urlComponents, mailboxManager: mailboxManager) + let newMessageView = ComposeMessageViewV2.mailTo(urlComponents: urlComponents, mailboxManager: mailboxManager) let viewController = UIHostingController(rootView: newMessageView) window?.rootViewController?.present(viewController, animated: true) } diff --git a/Mail/Views/Bottom sheets/ContactActionsView.swift b/Mail/Views/Bottom sheets/ContactActionsView.swift index 837b537e4..59b159d3f 100644 --- a/Mail/Views/Bottom sheets/ContactActionsView.swift +++ b/Mail/Views/Bottom sheets/ContactActionsView.swift @@ -99,7 +99,7 @@ struct ContactActionsView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 24) .sheet(item: $writtenToRecipient) { writtenToRecipient in - ComposeMessageView.writingTo(recipient: writtenToRecipient, mailboxManager: mailboxManager) + ComposeMessageViewV2.writingTo(recipient: writtenToRecipient, mailboxManager: mailboxManager) } .matomoView(view: [MatomoUtils.View.bottomSheet.displayName, "ContactActionsView"]) } diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift index d20126256..a6b110ed8 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift @@ -17,6 +17,8 @@ */ import Foundation +import InfomaniakCoreUI +import InfomaniakDI import MailCore import RealmSwift @@ -25,4 +27,25 @@ extension ComposeMessageViewV2 { let draft = Draft(localUUID: UUID().uuidString) return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) } + + static func replyOrForwardMessage(messageReply: MessageReply, mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + let draft = Draft.replying(reply: messageReply) + return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager, messageReply: messageReply) + } + + static func edit(draft: Draft, mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + @InjectService var matomo: MatomoUtils + matomo.track(eventWithCategory: .newMessage, name: "openFromDraft") + return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) + } + + static func writingTo(recipient: Recipient, mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + let draft = Draft.writing(to: recipient) + return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) + } + + static func mailTo(urlComponents: URLComponents, mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + let draft = Draft.mailTo(urlComponents: urlComponents) + return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) + } } diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 04b644c97..f2749a9b4 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -91,7 +91,7 @@ struct SplitView: View { } } .sheet(item: $navigationStore.messageReply) { messageReply in - ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) + ComposeMessageViewV2.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) } .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in Task { diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index 0d6eec308..a0d8612d3 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -61,7 +61,7 @@ struct MessageHeaderView: View { } } .sheet(item: $editedDraft) { editedDraft in - ComposeMessageView.editDraft(draft: editedDraft, mailboxManager: mailboxManager) + ComposeMessageViewV2.edit(draft: editedDraft, mailboxManager: mailboxManager) } } diff --git a/Mail/Views/ThreadListManagerView.swift b/Mail/Views/ThreadListManagerView.swift index b838b9af0..cb4acc998 100644 --- a/Mail/Views/ThreadListManagerView.swift +++ b/Mail/Views/ThreadListManagerView.swift @@ -54,7 +54,7 @@ struct ThreadListManagerView: View { } .animation(.easeInOut(duration: 0.25), value: splitViewManager.showSearch) .sheet(item: $editedMessageDraft) { draft in - ComposeMessageView.editDraft(draft: draft, mailboxManager: mailboxManager) + ComposeMessageViewV2.edit(draft: draft, mailboxManager: mailboxManager) } } } diff --git a/MailCore/Models/Draft.swift b/MailCore/Models/Draft.swift index 704628484..683aff923 100644 --- a/MailCore/Models/Draft.swift +++ b/MailCore/Models/Draft.swift @@ -207,6 +207,19 @@ public class Draft: Object, Codable, Identifiable { bcc: bcc) } + public static func mailTo(urlComponents: URLComponents) -> Draft { + let subject = urlComponents.getQueryItem(named: "subject") + let body = urlComponents.getQueryItem(named: "body")? + .replacingOccurrences(of: "\r", with: "") + .replacingOccurrences(of: "\n", with: "
") + let to = Recipient.createListUsing(listOfAddresses: urlComponents.path) + + Recipient.createListUsing(from: urlComponents, name: "to") + let cc = Recipient.createListUsing(from: urlComponents, name: "cc") + let bcc = Recipient.createListUsing(from: urlComponents, name: "bcc") + + return Draft(subject: subject ?? "", body: body ?? "", to: to, cc: cc, bcc: bcc) + } + public static func writing(to recipient: Recipient) -> Draft { return Draft(to: [recipient.detached()]) } From 175acfccd75139e51e49c1216c67beb0c6a4174b Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 16 Jun 2023 13:40:36 +0200 Subject: [PATCH 22/41] feat: Update recipients states --- .../New Message/V2/AutocompletionCell.swift | 18 +++++++++++------- .../New Message/V2/AutocompletionViewV2.swift | 8 ++++---- .../checked.imageset/checked.svg | 3 +-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Mail/Views/New Message/V2/AutocompletionCell.swift b/Mail/Views/New Message/V2/AutocompletionCell.swift index 25f3fe166..a1ce0dc8c 100644 --- a/Mail/Views/New Message/V2/AutocompletionCell.swift +++ b/Mail/Views/New Message/V2/AutocompletionCell.swift @@ -17,34 +17,38 @@ along with this program. If not, see . */ -import SwiftUI import MailCore +import MailResources +import SwiftUI struct AutocompletionCell: View { let addRecipient: @MainActor (Recipient) -> Void let recipient: Recipient var highlight: String? let alreadyAppend: Bool - + var body: some View { - HStack { + HStack(spacing: 12) { Button { addRecipient(recipient) } label: { RecipientCell(recipient: recipient, highlight: highlight) } - .disabled(alreadyAppend) + .allowsHitTesting(!alreadyAppend) + .opacity(alreadyAppend ? 0.5 : 1) if alreadyAppend { - Text("v") // TODO: Realm image + MailResourcesAsset.checked.swiftUIImage + .resizable() + .frame(width: 24, height: 24) + .foregroundColor(MailResourcesAsset.textTertiaryColor.swiftUIColor) } } - .padding(.horizontal, 8) } } struct AutocompletionCell_Previews: PreviewProvider { static var previews: some View { - AutocompletionCell(addRecipient: { _ in /* Preview */}, recipient: PreviewHelper.sampleRecipient1, alreadyAppend: false) + AutocompletionCell(addRecipient: { _ in /* Preview */ }, recipient: PreviewHelper.sampleRecipient1, alreadyAppend: false) } } diff --git a/Mail/Views/New Message/V2/AutocompletionViewV2.swift b/Mail/Views/New Message/V2/AutocompletionViewV2.swift index c06e51dc7..4937fc5d0 100644 --- a/Mail/Views/New Message/V2/AutocompletionViewV2.swift +++ b/Mail/Views/New Message/V2/AutocompletionViewV2.swift @@ -21,7 +21,7 @@ import RealmSwift import SwiftUI struct AutocompletionViewV2: View { - @State private var hasNoResult = false + @State private var shouldAddUserProposal = false @Binding var autocompletion: [Recipient] @Binding var currentSearch: String @@ -37,13 +37,13 @@ struct AutocompletionViewV2: View { addRecipient: addRecipient, recipient: recipient, highlight: currentSearch, - alreadyAppend: addedRecipients.contains(recipient) + alreadyAppend: addedRecipients.contains { $0.email == recipient.email && $0.name == recipient.name } ) IKDivider() } } - if hasNoResult { + if shouldAddUserProposal { AutocompletionCell( addRecipient: addRecipient, recipient: Recipient(email: currentSearch, name: ""), @@ -71,7 +71,7 @@ struct AutocompletionViewV2: View { withAnimation { autocompletion = autocompleteRecipients - hasNoResult = !currentSearch.isEmpty && realResults.isEmpty + shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == currentSearch) } } } diff --git a/MailResources/Assets.xcassets/checked.imageset/checked.svg b/MailResources/Assets.xcassets/checked.imageset/checked.svg index 47ce8050b..2f93067c8 100644 --- a/MailResources/Assets.xcassets/checked.imageset/checked.svg +++ b/MailResources/Assets.xcassets/checked.imageset/checked.svg @@ -1,4 +1,3 @@ - - + From f10d3317d17f7d451903ca9308bc1958ffdeb96a Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 16 Jun 2023 14:21:19 +0200 Subject: [PATCH 23/41] refactor: Clean ComposeMessageV2 code --- Mail/Components/RecipientCell.swift | 12 ------------ .../New Message/V2/ComposeMessageBodyViewV2.swift | 4 +++- Mail/Views/New Message/V2/ComposeMessageViewV2.swift | 4 +++- .../ComposeMessageCellRecipientsV2.swift | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Mail/Components/RecipientCell.swift b/Mail/Components/RecipientCell.swift index cd43298c2..478039e12 100644 --- a/Mail/Components/RecipientCell.swift +++ b/Mail/Components/RecipientCell.swift @@ -23,18 +23,6 @@ struct RecipientCell: View { let recipient: Recipient var highlight: String? - var name: AttributedString { - let data = AttributedString(recipient.name) - - return data - } - - var message: AttributedString { - let data = AttributedString(recipient.name) - - return data - } - var body: some View { HStack(spacing: 8) { AvatarView(avatarDisplayable: recipient, size: 40) diff --git a/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift index 81715de29..18c03328a 100644 --- a/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift @@ -34,6 +34,7 @@ struct ComposeMessageBodyViewV2: View { @StateRealmObject var draft: Draft @Binding var isLoadingContent: Bool + @Binding var editorFocus: Bool @ObservedObject var attachmentsManager: AttachmentsManager @ObservedObject var alert: NewMessageAlert @@ -55,7 +56,7 @@ struct ComposeMessageBodyViewV2: View { isShowingCamera: $isShowingCamera, isShowingFileSelection: $isShowingFileSelection, isShowingPhotoLibrary: $isShowingPhotoLibrary, - becomeFirstResponder: .constant(false), // TODO: Give real value + becomeFirstResponder: $editorFocus, blockRemoteContent: isRemoteContentBlocked ) .ignoresSafeArea(.all, edges: .bottom) @@ -169,6 +170,7 @@ struct ComposeMessageBodyViewV2_Previews: PreviewProvider { ComposeMessageBodyViewV2( draft: Draft(), isLoadingContent: .constant(false), + editorFocus: .constant(false), attachmentsManager: AttachmentsManager(draft: Draft(), mailboxManager: PreviewHelper.sampleMailboxManager), alert: NewMessageAlert(), messageReply: nil diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index db7aedfe0..bf020d05a 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -108,6 +108,7 @@ struct ComposeMessageViewV2: View { ComposeMessageBodyViewV2( draft: draft, isLoadingContent: $isLoadingContent, + editorFocus: $editorFocus, attachmentsManager: attachmentsManager, alert: alert, messageReply: messageReply @@ -145,6 +146,8 @@ struct ComposeMessageViewV2: View { .disabled(isSendButtonDisabled) } } + } + .interactiveDismissDisabled() .customAlert(isPresented: $alert.isShowing) { switch alert.state { case let .link(handler): @@ -161,7 +164,6 @@ struct ComposeMessageViewV2: View { } } .matomoView(view: ["ComposeMessage"]) - } } private func didTouchDismiss() { diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index e331b72f5..1ac712d7a 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -81,7 +81,7 @@ struct ComposeMessageCellRecipientsV2: View { if Constants.isEmailAddress(recipient.email) { withAnimation { - recipients.append(recipient) + $recipients.append(recipient) } currentText = "" } else { From c86fe14f840ec70cbd49f6550dbde70c6447683f Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 16 Jun 2023 14:30:41 +0200 Subject: [PATCH 24/41] feat: Display loading overlay --- .../New Message/V2/ComposeMessageViewV2.swift | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift index bf020d05a..e8cef662a 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageViewV2.swift @@ -18,6 +18,7 @@ import InfomaniakCoreUI import InfomaniakDI +import Introspect import MailCore import MailResources import RealmSwift @@ -63,7 +64,6 @@ struct ComposeMessageViewV2: View { @State private var isLoadingContent: Bool @State private var isShowingCancelAttachmentsError = false - @State private var autocompletionField: ComposeViewFieldType? @State private var autocompletionType: ComposeViewFieldType? @State private var editorFocus = false @@ -130,6 +130,16 @@ struct ComposeMessageViewV2: View { DraftManager.shared.syncDraft(mailboxManager: mailboxManager) } } + .overlay { + if isLoadingContent { + ProgressView() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(MailResourcesAsset.backgroundColor.swiftUIColor) + } + } + .introspectScrollView { scrollView in + scrollView.keyboardDismissMode = .interactive + } .navigationTitle(MailResourcesStrings.Localizable.buttonNewMessage) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -148,22 +158,22 @@ struct ComposeMessageViewV2: View { } } .interactiveDismissDisabled() - .customAlert(isPresented: $alert.isShowing) { - switch alert.state { - case let .link(handler): - AddLinkView(actionHandler: handler) - case let .emptySubject(handler): - EmptySubjectView(actionHandler: handler) - case .none: - EmptyView() - } + .customAlert(isPresented: $alert.isShowing) { + switch alert.state { + case let .link(handler): + AddLinkView(actionHandler: handler) + case let .emptySubject(handler): + EmptySubjectView(actionHandler: handler) + case .none: + EmptyView() } - .customAlert(isPresented: $isShowingCancelAttachmentsError) { - AttachmentsUploadInProgressErrorView { - dismiss() - } + } + .customAlert(isPresented: $isShowingCancelAttachmentsError) { + AttachmentsUploadInProgressErrorView { + dismiss() } - .matomoView(view: ["ComposeMessage"]) + } + .matomoView(view: ["ComposeMessage"]) } private func didTouchDismiss() { From 1ebd518f3cbeb4ce889874b48fc207fe3e06378e Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 08:28:34 +0200 Subject: [PATCH 25/41] feat: Get focus when touch field --- .../New Message/V2/ComposeMessageHeaderViewV2.swift | 12 ++++++++++-- .../ComposeMessageCellRecipientsV2.swift | 9 +++++++-- .../ComposeMessageCellStaticTextV2.swift | 3 ++- .../Header Cells/ComposeMessageCellTextFieldV2.swift | 9 ++++++++- MailCore/UI/UIConstants.swift | 2 ++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift index 544911878..de911fb00 100644 --- a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift @@ -32,7 +32,7 @@ struct ComposeMessageHeaderViewV2: View { @Binding var autocompletionType: ComposeViewFieldType? var body: some View { - VStack { + VStack(spacing: UIConstants.composeViewVerticalSpacing) { ComposeMessageCellStaticTextV2( autocompletionType: $autocompletionType, type: .from, @@ -43,6 +43,7 @@ struct ComposeMessageHeaderViewV2: View { recipients: $draft.to, showRecipientsFields: $showRecipientsFields, autocompletionType: $autocompletionType, + focusedField: _focusedField, type: .to ) @@ -51,6 +52,7 @@ struct ComposeMessageHeaderViewV2: View { recipients: $draft.cc, showRecipientsFields: $showRecipientsFields, autocompletionType: $autocompletionType, + focusedField: _focusedField, type: .cc ) @@ -58,11 +60,17 @@ struct ComposeMessageHeaderViewV2: View { recipients: $draft.bcc, showRecipientsFields: $showRecipientsFields, autocompletionType: $autocompletionType, + focusedField: _focusedField, type: .bcc ) } - ComposeMessageCellTextFieldV2(text: $draft.subject, autocompletionType: $autocompletionType, type: .subject) + ComposeMessageCellTextFieldV2( + text: $draft.subject, + autocompletionType: $autocompletionType, + focusedField: _focusedField, + type: .subject + ) } .padding(.horizontal, 16) .onAppear { diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift index 1ac712d7a..e71fe5f55 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -31,16 +31,18 @@ struct ComposeMessageCellRecipientsV2: View { @Binding var showRecipientsFields: Bool @Binding var autocompletionType: ComposeViewFieldType? + @FocusState var focusedField: ComposeViewFieldType? + let type: ComposeViewFieldType var body: some View { - VStack { + VStack(spacing: UIConstants.composeViewVerticalSpacing) { if autocompletionType == nil || autocompletionType == type { HStack { Text(type.title) .textStyle(.bodySecondary) - RecipientFieldV2(currentText: $currentText, recipients: $recipients, type: type) { + RecipientFieldV2(currentText: $currentText, recipients: $recipients, focusedField: _focusedField, type: type) { if let bestMatch = autocompletion.first { addNewRecipient(bestMatch) } @@ -64,6 +66,9 @@ struct ComposeMessageCellRecipientsV2: View { ) } } + .onTapGesture { + focusedField = type + } .onChange(of: currentText) { newValue in withAnimation { if newValue.isEmpty { diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift index bb8fe3ab1..69af623ca 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift @@ -16,6 +16,7 @@ along with this program. If not, see . */ +import MailCore import SwiftUI struct ComposeMessageCellStaticTextV2: View { @@ -26,7 +27,7 @@ struct ComposeMessageCellStaticTextV2: View { var body: some View { if autocompletionType == nil { - VStack { + VStack(spacing: UIConstants.composeViewVerticalSpacing) { HStack { Text(type.title) .textStyle(.bodySecondary) diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift index e17f55b8e..07cfaf4a4 100644 --- a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift +++ b/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift @@ -16,26 +16,33 @@ along with this program. If not, see . */ +import MailCore import SwiftUI struct ComposeMessageCellTextFieldV2: View { @Binding var text: String @Binding var autocompletionType: ComposeViewFieldType? + @FocusState var focusedField: ComposeViewFieldType? + let type: ComposeViewFieldType var body: some View { if autocompletionType == nil { - VStack { + VStack(spacing: UIConstants.composeViewVerticalSpacing) { HStack { Text(type.title) .textStyle(.bodySecondary) TextField("", text: $text) + .focused($focusedField, equals: .subject) } .frame(maxWidth: .infinity, alignment: .leading) IKDivider() } + .onTapGesture { + focusedField = type + } } } } diff --git a/MailCore/UI/UIConstants.swift b/MailCore/UI/UIConstants.swift index 3c5fb0a56..f2ebd3ac2 100644 --- a/MailCore/UI/UIConstants.swift +++ b/MailCore/UI/UIConstants.swift @@ -101,6 +101,8 @@ public enum UIConstants { public static let buttonsRadius: CGFloat = 16 public static let buttonsIconSize: CGFloat = 16 + public static let composeViewVerticalSpacing: CGFloat = 12 + public static let bottomBarVerticalPadding: CGFloat = 8 public static let bottomBarSmallVerticalPadding: CGFloat = 4 public static let bottomBarHorizontalMinimumSpace: CGFloat = 8 From 15b5ca8e7eaee450188dfe4e594327419a092166 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 08:32:39 +0200 Subject: [PATCH 26/41] refactor: Remove old ComposeMessageView --- .../New Message/ComposeMessageView.swift | 431 ------------------ Mail/Views/Thread List/ThreadListView.swift | 9 - 2 files changed, 440 deletions(-) delete mode 100644 Mail/Views/New Message/ComposeMessageView.swift diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift deleted file mode 100644 index 1c99199bb..000000000 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ /dev/null @@ -1,431 +0,0 @@ -/* - Infomaniak Mail - iOS App - Copyright (C) 2022 Infomaniak Network SA - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -import InfomaniakCore -import InfomaniakCoreUI -import InfomaniakDI -import Introspect -import MailCore -import MailResources -import PhotosUI -import RealmSwift -import Sentry -import SwiftUI - -struct ComposeMessageView: View { - @Environment(\.dismiss) private var dismiss - - @LazyInjectService private var matomo: MatomoUtils - - @State private var mailboxManager: MailboxManager - - @StateRealmObject var draft: Draft - @StateObject private var editor = RichTextEditorModel() - @State private var showCc = false - @State private var isLoadingContent: Bool - @FocusState private var focusedField: ComposeViewFieldType? { - willSet { - let editorInFocus = (newValue == .editor) - editorFocus = editorInFocus - } - } - - @State private var editorFocus = false - - @State private var addRecipientHandler: ((Recipient) -> Void)? - @State private var autocompletion: [Recipient] = [] - @State private var unknownRecipientAutocompletion = "" - - @State private var isShowingCamera = false - @State private var isShowingFileSelection = false - @State private var isShowingPhotoLibrary = false - @StateObject private var attachmentsManager: AttachmentsManager - @State private var isShowingCancelAttachmentsError = false - - @State var scrollView: UIScrollView? - - @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) - || !attachmentsManager.allAttachmentsUploaded - } - - private var shouldDisplayAutocompletion: Bool { - return (!autocompletion.isEmpty || !unknownRecipientAutocompletion.isEmpty) && focusedField != nil - } - - private var isRemoteContentBlocked: Bool { - return UserDefaults.shared.displayExternalContent == .askMe && messageReply?.message.localSafeDisplay == false - } - - private init(mailboxManager: MailboxManager, draft: Draft, messageReply: MessageReply? = nil) { - self.messageReply = messageReply - _mailboxManager = State(initialValue: mailboxManager) - 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) - _attachmentsManager = StateObject(wrappedValue: AttachmentsManager(draft: draft, mailboxManager: mailboxManager)) - _isLoadingContent = State(initialValue: (draft.messageUid != nil && draft.remoteUUID.isEmpty) || messageReply != nil) - } - - var body: some View { - NavigationView { - ScrollView(.vertical, showsIndicators: true) { - VStack(spacing: 0) { - if !shouldDisplayAutocompletion { - NewMessageCell(type: .from, - isFirstCell: true) { - Text(mailboxManager.mailbox.email) - .textStyle(.body) - } - } - - recipientCell(type: .to) - - if showCc { - recipientCell(type: .cc) - recipientCell(type: .bcc) - } - - // Show the rest of the view, or the autocompletion list - if shouldDisplayAutocompletion { - AutocompletionView(autocompletion: $autocompletion, - unknownRecipientAutocompletion: $unknownRecipientAutocompletion) { recipient in - matomo.track(eventWithCategory: .newMessage, name: "addNewRecipient") - addRecipientHandler?(recipient) - } - } else { - NewMessageCell(type: .subject, - focusedField: _focusedField) { - TextField("", text: $draft.subject) - .focused($focusedField, equals: .subject) - } - - AttachmentsHeaderView(attachmentsManager: attachmentsManager) - - RichTextEditor(model: editor, - body: $draft.body, - alert: $alert, - isShowingCamera: $isShowingCamera, - isShowingFileSelection: $isShowingFileSelection, - isShowingPhotoLibrary: $isShowingPhotoLibrary, - becomeFirstResponder: $editorFocus, - blockRemoteContent: isRemoteContentBlocked) - .ignoresSafeArea(.all, edges: .bottom) - .frame(height: editor.height + 20) - .padding([.vertical], 10) - } - } - } - .overlay { - if isLoadingContent { - ProgressView() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(MailResourcesAsset.backgroundColor.swiftUIColor) - } - } - .introspectScrollView { scrollView in - guard self.scrollView != scrollView else { return } - self.scrollView = scrollView - scrollView.keyboardDismissMode = .interactive - } - .onChange(of: editor.height) { _ in - guard let scrollView = scrollView else { return } - - let fullSize = scrollView.contentSize.height - let realPosition = (fullSize - editor.height) + editor.cursorPosition - - let rect = CGRect(x: 0, y: realPosition, width: 1, height: 1) - scrollView.scrollRectToVisible(rect, animated: true) - } - .navigationBarTitleDisplayMode(.inline) - .navigationBarBackButtonHidden(true) - .navigationBarItems( - leading: Button(action: closeDraft) { - Label(MailResourcesStrings.Localizable.buttonClose, systemImage: "xmark") - }, - trailing: Button(action: sendDraft) { - MailResourcesAsset.send.swiftUIImage - } - .disabled(isSendButtonDisabled) - ) - .background(MailResourcesAsset.backgroundColor.swiftUIColor) - } - .onAppear { - switch messageReply?.replyMode { - case .reply, .replyAll: - focusedField = .editor - default: - focusedField = .to - } - } - .onDisappear { - Task { - DraftManager.shared.syncDraft(mailboxManager: mailboxManager) - } - } - .interactiveDismissDisabled() - .fullScreenCover(isPresented: $isShowingCamera) { - CameraPicker { data in - attachmentsManager.importAttachments(attachments: [data]) - } - .ignoresSafeArea() - } - .sheet(isPresented: $isShowingFileSelection) { - DocumentPicker(pickerType: .selectContent([.item]) { urls in - attachmentsManager.importAttachments(attachments: urls) - }) - .ignoresSafeArea() - } - .sheet(isPresented: $isShowingPhotoLibrary) { - ImagePicker { results in - attachmentsManager.importAttachments(attachments: results) - } - .ignoresSafeArea() - } - .customAlert(isPresented: $alert.isShowing) { - switch alert.state { - case .link(let handler): - AddLinkView(actionHandler: handler) - case .emptySubject(let handler): - EmptySubjectView(actionHandler: handler) - case .none: - EmptyView() - } - } - .customAlert(isPresented: $isShowingCancelAttachmentsError) { - AttachmentsUploadInProgressErrorView { - dismiss() - } - } - .task { - await prepareCompleteDraft() - } - .task { - await prepareReplyForwardBodyAndAttachments() - await setSignature() - } - .navigationViewStyle(.stack) - .defaultAppStorage(.shared) - .matomoView(view: ["ComposeMessage"]) - } - - @ViewBuilder - private func recipientCell(type: ComposeViewFieldType) -> some View { - let shouldDisplayField = !shouldDisplayAutocompletion || focusedField == type - if shouldDisplayField { - NewMessageCell(type: type, - focusedField: _focusedField, - showCc: type == .to ? $showCc : nil) { - RecipientField(recipients: binding(for: type), - autocompletion: $autocompletion, - unknownRecipientAutocompletion: $unknownRecipientAutocompletion, - addRecipientHandler: $addRecipientHandler, - focusedField: _focusedField, - type: type) - } - } - } - - private func binding(for type: ComposeViewFieldType) -> Binding> { - let binding: Binding> - switch type { - case .to: - binding = $draft.to - case .cc: - binding = $draft.cc - case .bcc: - binding = $draft.bcc - default: - fatalError("Unhandled binding \(type)") - } - return binding - } - - private func closeDraft() { - guard attachmentsManager.allAttachmentsUploaded else { - isShowingCancelAttachmentsError = true - return - } - - dismiss() - } - - private func sendDraft() { - guard !draft.subject.isEmpty else { - matomo.track(eventWithCategory: .newMessage, name: "sendWithoutSubject") - alert.state = .emptySubject(handler: send) - return - } - - send() - } - - private func send() { - matomo.trackSendMessage(numberOfTo: draft.to.count, numberOfCc: draft.cc.count, numberOfBcc: draft.bcc.count) - if let liveDraft = draft.thaw() { - try? liveDraft.realm?.write { - liveDraft.action = .send - } - } - 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) - SentrySDK.capture(message: "Error thrown in prepareCompleteDraft()") { scope in - scope.setLevel(.error) - scope.setContext(value: ["uid": "\(String(describing: draft.messageUid))", - "error": error], - key: "message") - } - } - } - - private func prepareReplyForwardBodyAndAttachments() async { - guard let messageReply else { return } - - let prepareTask = Task.detached { - try await prepareBody(message: messageReply.message, replyMode: messageReply.replyMode) - try await prepareAttachments(message: messageReply.message, replyMode: messageReply.replyMode) - } - - do { - _ = try await prepareTask.value - - isLoadingContent = false - } catch { - dismiss() - IKSnackBar.showSnackBar(message: MailError.unknownError.localizedDescription) - SentrySDK.capture(message: "Error thrown in prepareReplyForwardBodyAndAttachments()") { scope in - scope.setLevel(.error) - scope.setContext(value: ["uid": "\(String(describing: messageReply.message.uid))", - "error": error], - key: "message") - } - } - } - - private func setSignature() async { - if draft.identityId == nil || draft.identityId?.isEmpty == true, - let signatureResponse = mailboxManager.getSignatureResponse() { - $draft.identityId.wrappedValue = "\(signatureResponse.defaultSignatureId)" - guard let signature = signatureResponse.default else { - return - } - - let html = "

\(signature.content)
" - var signaturePosition = draft.body.endIndex - if messageReply != nil { - switch signature.position { - case .beforeReplyMessage: - signaturePosition = draft.body.startIndex - case .afterReplyMessage: - signaturePosition = draft.body.endIndex - } - } - $draft.body.wrappedValue.insert(contentsOf: html, at: signaturePosition) - } - } - - 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 { - static func newMessage(mailboxManager: MailboxManager) -> ComposeMessageView { - return ComposeMessageView(mailboxManager: mailboxManager, draft: Draft(localUUID: UUID().uuidString)) - } - - static func replyOrForwardMessage(messageReply: MessageReply, mailboxManager: MailboxManager) -> ComposeMessageView { - return ComposeMessageView( - mailboxManager: mailboxManager, - draft: .replying(reply: messageReply), - messageReply: messageReply - ) - } - - static func editDraft(draft: Draft, mailboxManager: MailboxManager) -> ComposeMessageView { - @InjectService var matomo: MatomoUtils - matomo.track(eventWithCategory: .newMessage, name: "openFromDraft") - 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 = Draft.mailTo(subject: urlComponents.getQueryItem(named: "subject"), - body: urlComponents.getQueryItem(named: "body")? - .replacingOccurrences(of: "\r", with: "") - .replacingOccurrences(of: "\n", with: "
"), - to: Recipient.createListUsing(listOfAddresses: urlComponents.path) - + Recipient.createListUsing(from: urlComponents, name: "to"), - cc: Recipient.createListUsing(from: urlComponents, name: "cc"), - bcc: Recipient.createListUsing(from: urlComponents, name: "bcc")) - return ComposeMessageView(mailboxManager: mailboxManager, draft: draft) - } -} - -struct ComposeMessageView_Previews: PreviewProvider { - static var previews: some View { - ComposeMessageView.newMessage(mailboxManager: PreviewHelper.sampleMailboxManager) - } -} diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 66c777669..4c336bd0c 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -44,7 +44,6 @@ struct ThreadListView: View { @AppStorage(UserDefaults.shared.key(.threadDensity)) private var threadDensity = DefaultPreferences.threadDensity @AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor - @State private var isShowingV2 = false @State private var isShowingComposeNewMessageView = false @State private var fetchingTask: Task? @State private var isRefreshing = false @@ -220,11 +219,6 @@ struct ThreadListView: View { matomo.track(eventWithCategory: .newMessage, name: "openFromFab") isShowingComposeNewMessageView.toggle() } - .overlay(alignment: .bottomLeading, content: { - MailButton(icon: MailResourcesAsset.pencil, label: "Show V2") { - isShowingV2 = true - } - }) .onAppear { networkMonitor.start() if viewModel.isCompact { @@ -251,9 +245,6 @@ struct ThreadListView: View { } } .sheet(isPresented: $isShowingComposeNewMessageView) { - ComposeMessageView.newMessage(mailboxManager: viewModel.mailboxManager) - } - .sheet(isPresented: $isShowingV2) { ComposeMessageViewV2.newMessage(mailboxManager: viewModel.mailboxManager) } .customAlert(item: $flushAlert) { item in From 0c3b4d8adfa6a2d4157a633e66d3b151fbaa74f8 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 08:38:45 +0200 Subject: [PATCH 27/41] refactor: Remove old compenents --- Mail/Components/RecipientField.swift | 159 ------------------ Mail/Views/New Message/AddRecipientCell.swift | 57 ------- .../New Message/AutocompletionView.swift | 65 ------- Mail/Views/New Message/NewMessageCell.swift | 97 ----------- .../New Message/RecipientsTextField.swift | 97 ----------- .../New Message/V2/RecipientFieldV2.swift | 19 ++- .../V2/RecipientsTextFieldV2.swift | 30 ++++ 7 files changed, 42 insertions(+), 482 deletions(-) delete mode 100644 Mail/Components/RecipientField.swift delete mode 100644 Mail/Views/New Message/AddRecipientCell.swift delete mode 100644 Mail/Views/New Message/AutocompletionView.swift delete mode 100644 Mail/Views/New Message/NewMessageCell.swift delete mode 100644 Mail/Views/New Message/RecipientsTextField.swift diff --git a/Mail/Components/RecipientField.swift b/Mail/Components/RecipientField.swift deleted file mode 100644 index 06abdd0d3..000000000 --- a/Mail/Components/RecipientField.swift +++ /dev/null @@ -1,159 +0,0 @@ -/* - Infomaniak Mail - iOS App - Copyright (C) 2022 Infomaniak Network SA - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -import InfomaniakCore -import InfomaniakCoreUI -import InfomaniakDI -import MailCore -import MailResources -import RealmSwift -import SwiftUI -import WrappingHStack - -struct RecipientField: View { - @State private var currentText = "" - @State private var keyboardHeight: CGFloat = 0 - - @Binding var recipients: RealmSwift.List - @Binding var autocompletion: [Recipient] - @Binding var unknownRecipientAutocompletion: String - @MainActor @Binding var addRecipientHandler: ((Recipient) -> Void)? - - @FocusState var focusedField: ComposeViewFieldType? - - let type: ComposeViewFieldType - - /// A trimmed view on `currentText` - private var trimmedInputText: String { - currentText.trimmingCharacters(in: .whitespacesAndNewlines) - } - - var body: some View { - VStack { - if !recipients.isEmpty { - WrappingHStack(recipients.indices, spacing: .constant(8), lineSpacing: 8) { i in - RecipientChip(recipient: recipients[i], fieldType: type, focusedField: _focusedField) { - remove(recipientAt: i) - } switchFocusHandler: { - switchFocus() - } - .focused($focusedField, equals: .chip(type.hashValue, recipients[i])) - } - .alignmentGuide(.newMessageCellAlignment) { d in d[.top] + 21 } - } - - RecipientsTextFieldView(text: $currentText, onSubmit: submitTextField, onBackspace: handleBackspaceTextField) - .focused($focusedField, equals: type) - } - .onChange(of: currentText) { _ in - updateAutocompletion() - addRecipientHandler = add(recipient:) - } - .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { output in - if let userInfo = output.userInfo, - let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { - keyboardHeight = keyboardFrame.height - } - } - .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification)) { _ in - keyboardHeight = 0 - } - } - - @MainActor private func submitTextField() { - // use first autocompletion result or try to validate current input - guard let recipient = autocompletion.first else { - let guessRecipient = Recipient(email: trimmedInputText, name: "") - add(recipient: guessRecipient) - return - } - - add(recipient: recipient) - } - - @MainActor private func add(recipient: Recipient) { - @InjectService var matomo: MatomoUtils - matomo.track(eventWithCategory: .newMessage, action: .input, name: "addNewRecipient") - - if Constants.isEmailAddress(recipient.email) { - withAnimation { - $recipients.append(recipient) - } - currentText = "" - } else { - IKSnackBar.showSnackBar( - message: MailResourcesStrings.Localizable.addUnknownRecipientInvalidEmail, - anchor: keyboardHeight - ) - } - } - - @MainActor private func remove(recipientAt: Int) { - withAnimation { - $recipients.remove(at: recipientAt) - } - } - - private func handleBackspaceTextField(isTextEmpty: Bool) { - if let recipient = recipients.last, isTextEmpty { - focusedField = .chip(type.hashValue, recipient) - } - } - - private func updateAutocompletion() { - let trimmedCurrentText = trimmedInputText - - let contactManager = AccountManager.instance.currentContactManager - let autocompleteContacts = contactManager?.contacts(matching: trimmedCurrentText) ?? [] - let autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) } - - withAnimation { - autocompletion = autocompleteRecipients.filter { !recipients.map(\.email).contains($0.email) } - - if !trimmedCurrentText.isEmpty && !autocompletion - .contains(where: { $0.email.caseInsensitiveCompare(trimmedCurrentText) == .orderedSame }) { - unknownRecipientAutocompletion = trimmedCurrentText - } else { - unknownRecipientAutocompletion = "" - } - } - } - - private func switchFocus() { - guard case .chip(let hash, let recipient) = focusedField else { return } - - if recipient == recipients.last { - focusedField = type - } else if let recipientIndex = recipients.firstIndex(of: recipient) { - focusedField = .chip(hash, recipients[recipientIndex + 1]) - } - } -} - -struct RecipientField_Previews: PreviewProvider { - static var previews: some View { - RecipientField(recipients: .constant([ - PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), - autocompletion: .constant([]), - unknownRecipientAutocompletion: .constant(""), - addRecipientHandler: .constant { _ in /* Preview */ }, - focusedField: .init(), - type: .to) - } -} diff --git a/Mail/Views/New Message/AddRecipientCell.swift b/Mail/Views/New Message/AddRecipientCell.swift deleted file mode 100644 index ba8c6c650..000000000 --- a/Mail/Views/New Message/AddRecipientCell.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* - Infomaniak Mail - iOS App - Copyright (C) 2022 Infomaniak Network SA - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -import MailCore -import SwiftUI -import MailResources - -struct AddRecipientCell: View { - @AppStorage(UserDefaults.shared.key(.accentColor)) private var accentColor = DefaultPreferences.accentColor - - let recipientEmail: String - - var body: some View { - HStack(spacing: 8) { - Circle() - .fill(accentColor.primary.swiftUIColor) - .frame(width: 40, height: 40) - .overlay { - MailResourcesAsset.userBold.swiftUIImage - .resizable() - .foregroundColor(accentColor.onAccent.swiftUIColor) - .frame(width: 24, height: 24) - } - - VStack(alignment: .leading, spacing: 0) { - Text(MailResourcesStrings.Localizable.addUnknownRecipientTitle) - .textStyle(.bodyMedium) - Text(recipientEmail) - .textStyle(.bodySecondary) - } - - Spacer() - } - .lineLimit(1) - } -} - -struct AddRecipientCell_Previews: PreviewProvider { - static var previews: some View { - AddRecipientCell(recipientEmail: "") - } -} diff --git a/Mail/Views/New Message/AutocompletionView.swift b/Mail/Views/New Message/AutocompletionView.swift deleted file mode 100644 index 4acecb730..000000000 --- a/Mail/Views/New Message/AutocompletionView.swift +++ /dev/null @@ -1,65 +0,0 @@ -/* - Infomaniak Mail - iOS App - Copyright (C) 2022 Infomaniak Network SA - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -import MailCore -import MailResources -import SwiftUI - -struct AutocompletionView: View { - @Binding var autocompletion: [Recipient] - @Binding var unknownRecipientAutocompletion: String - - let onSelect: (Recipient) -> Void - - var body: some View { - LazyVStack { - ForEach(autocompletion) { recipient in - VStack(alignment: .leading, spacing: 8) { - Button { - onSelect(recipient) - } label: { - RecipientCell(recipient: recipient) - } - .padding(.horizontal, 8) - - IKDivider() - } - } - - if !unknownRecipientAutocompletion.isEmpty { - Button { - onSelect(Recipient(email: unknownRecipientAutocompletion, name: "")) - } label: { - AddRecipientCell(recipientEmail: unknownRecipientAutocompletion) - } - .padding(.horizontal, 8) - } - } - .padding(.top, 8) - .padding(.horizontal, 8) - } -} - -struct AutocompletionView_Previews: PreviewProvider { - static var previews: some View { - AutocompletionView(autocompletion: .constant([ - PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ]), - unknownRecipientAutocompletion: .constant("")) { _ in /* Preview */ } - } -} diff --git a/Mail/Views/New Message/NewMessageCell.swift b/Mail/Views/New Message/NewMessageCell.swift deleted file mode 100644 index fdd8f1520..000000000 --- a/Mail/Views/New Message/NewMessageCell.swift +++ /dev/null @@ -1,97 +0,0 @@ -/* - Infomaniak Mail - iOS App - Copyright (C) 2022 Infomaniak Network SA - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -import MailCore -import MailResources -import RealmSwift -import SwiftUI - -extension VerticalAlignment { - private struct NewMessageCellAlignment: AlignmentID { - static func defaultValue(in context: ViewDimensions) -> CGFloat { - context[.firstTextBaseline] - } - } - - static let newMessageCellAlignment = VerticalAlignment(NewMessageCellAlignment.self) -} - -struct NewMessageCell: View where Content: View { - let type: ComposeViewFieldType - let focusedField: FocusState? - let showCc: Binding? - let isFirstCell: Bool - let content: Content - - let verticalPadding: CGFloat = 12 - - init(type: ComposeViewFieldType, - focusedField: FocusState? = nil, - showCc: Binding? = nil, - isFirstCell: Bool = false, - @ViewBuilder _ content: () -> Content) { - self.type = type - self.focusedField = focusedField - self.showCc = showCc - self.isFirstCell = isFirstCell - self.content = content() - } - - var body: some View { - HStack(alignment: .newMessageCellAlignment) { - Text(type.title) - .textStyle(.bodySecondary) - - content - - Spacer() - - if let showCc = showCc { - ChevronButton(isExpanded: showCc) - } - } - .padding(.horizontal, 16) - .padding(.top, isFirstCell ? 0 : verticalPadding) - .padding(.bottom, verticalPadding) - .onTapGesture { - focusedField?.wrappedValue = type - } - - IKDivider() - .padding(.horizontal, 8) - } -} - -struct NewMessageCell_Previews: PreviewProvider { - static var previews: some View { - VStack { - NewMessageCell(type: .to, - showCc: .constant(false)) { - RecipientField(recipients: .constant([PreviewHelper.sampleRecipient1].toRealmList()), - autocompletion: .constant([]), - unknownRecipientAutocompletion: .constant(""), - addRecipientHandler: .constant { _ in /* Preview */ }, - focusedField: .init(), - type: .to) - } - NewMessageCell(type: .subject) { - TextField("", text: .constant("")) - } - } - } -} diff --git a/Mail/Views/New Message/RecipientsTextField.swift b/Mail/Views/New Message/RecipientsTextField.swift deleted file mode 100644 index 4f9c71653..000000000 --- a/Mail/Views/New Message/RecipientsTextField.swift +++ /dev/null @@ -1,97 +0,0 @@ -/* - Infomaniak Mail - iOS App - Copyright (C) 2022 Infomaniak Network SA - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -import SwiftUI -import UIKit - -struct RecipientsTextFieldView: UIViewRepresentable { - @Binding var text: String - - let onSubmit: () -> Void - let onBackspace: (Bool) -> Void - - func makeUIView(context: Context) -> UITextField { - let textField = RecipientsTextField() - textField.delegate = context.coordinator - textField.addTarget(context.coordinator, action: #selector(context.coordinator.textDidChanged(_:)), for: .editingChanged) - textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - textField.setContentHuggingPriority(.defaultHigh, for: .vertical) - textField.onBackspace = onBackspace - return textField - } - - func updateUIView(_ textField: UITextField, context: Context) { - guard textField.text != text else { return } - textField.text = text - } - - func makeCoordinator() -> Coordinator { - return Coordinator(self) - } - - class Coordinator: NSObject, UITextFieldDelegate { - let parent: RecipientsTextFieldView - - init(_ parent: RecipientsTextFieldView) { - self.parent = parent - } - - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - guard textField.text?.isEmpty == false else { - textField.resignFirstResponder() - return true - } - - parent.onSubmit() - return true - } - - @objc func textDidChanged(_ textField: UITextField) { - parent.text = textField.text ?? "" - } - } -} - -/* - * We need to create our own UITextField to benefit from the `deleteBackward()` function - */ -class RecipientsTextField: UITextField { - var onBackspace: ((Bool) -> Void)? - - override init(frame: CGRect) { - super.init(frame: frame) - setUpView() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - setUpView() - } - - private func setUpView() { - textContentType = .emailAddress - keyboardType = .emailAddress - autocapitalizationType = .none - autocorrectionType = .no - } - - override func deleteBackward() { - onBackspace?(text?.isEmpty == true) - super.deleteBackward() - } -} diff --git a/Mail/Views/New Message/V2/RecipientFieldV2.swift b/Mail/Views/New Message/V2/RecipientFieldV2.swift index 3630513af..8d694ad8e 100644 --- a/Mail/Views/New Message/V2/RecipientFieldV2.swift +++ b/Mail/Views/New Message/V2/RecipientFieldV2.swift @@ -25,6 +25,16 @@ import RealmSwift import SwiftUI import WrappingHStack +extension VerticalAlignment { + private struct NewMessageCellAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[.firstTextBaseline] + } + } + + static let newMessageCellAlignment = VerticalAlignment(NewMessageCellAlignment.self) +} + struct RecipientFieldV2: View { @State private var keyboardHeight: CGFloat = 0 @@ -94,13 +104,8 @@ struct RecipientFieldV2: View { struct RecipientFieldV2_Previews: PreviewProvider { static var previews: some View { - RecipientField(recipients: .constant([ + RecipientFieldV2(currentText: .constant(""), recipients: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ].toRealmList()), - autocompletion: .constant([]), - unknownRecipientAutocompletion: .constant(""), - addRecipientHandler: .constant { _ in /* Preview */ }, - focusedField: .init(), - type: .to) + ].toRealmList()), type: .to) } } diff --git a/Mail/Views/New Message/V2/RecipientsTextFieldV2.swift b/Mail/Views/New Message/V2/RecipientsTextFieldV2.swift index 19151c10f..f31b84f90 100644 --- a/Mail/Views/New Message/V2/RecipientsTextFieldV2.swift +++ b/Mail/Views/New Message/V2/RecipientsTextFieldV2.swift @@ -66,3 +66,33 @@ struct RecipientsTextFieldV2View: UIViewRepresentable { } } } + +/* + * We need to create our own UITextField to benefit from the `deleteBackward()` function + */ +class RecipientsTextField: UITextField { + var onBackspace: ((Bool) -> Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + setUpView() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setUpView() + } + + private func setUpView() { + textContentType = .emailAddress + keyboardType = .emailAddress + autocapitalizationType = .none + autocorrectionType = .no + } + + override func deleteBackward() { + onBackspace?(text?.isEmpty == true) + super.deleteBackward() + } +} + From 6657253fb64948f24d94d2ec0e85ecbe1e7461a3 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 08:45:01 +0200 Subject: [PATCH 28/41] refactor: Remove V2 for main view --- .../{V2 => }/AutocompletionCell.swift | 0 .../{V2 => }/AutocompletionViewV2.swift | 0 .../{V2 => }/ComposeMessageBodyViewV2.swift | 0 .../{V2 => }/ComposeMessageHeaderViewV2.swift | 0 ...ift => ComposeMessageView+Extension.swift} | 22 +++++++++---------- ...eViewV2.swift => ComposeMessageView.swift} | 6 ++--- .../ComposeMessageCellRecipientsV2.swift | 0 .../ComposeMessageCellStaticTextV2.swift | 0 .../ComposeMessageCellTextFieldV2.swift | 0 .../{V2 => }/RecipientFieldV2.swift | 0 .../{V2 => }/RecipientsTextFieldV2.swift | 0 11 files changed, 14 insertions(+), 14 deletions(-) rename Mail/Views/New Message/{V2 => }/AutocompletionCell.swift (100%) rename Mail/Views/New Message/{V2 => }/AutocompletionViewV2.swift (100%) rename Mail/Views/New Message/{V2 => }/ComposeMessageBodyViewV2.swift (100%) rename Mail/Views/New Message/{V2 => }/ComposeMessageHeaderViewV2.swift (100%) rename Mail/Views/New Message/{V2/ComposeMessageViewV2+Extension.swift => ComposeMessageView+Extension.swift} (69%) rename Mail/Views/New Message/{V2/ComposeMessageViewV2.swift => ComposeMessageView.swift} (97%) rename Mail/Views/New Message/{V2 => }/Header Cells/ComposeMessageCellRecipientsV2.swift (100%) rename Mail/Views/New Message/{V2 => }/Header Cells/ComposeMessageCellStaticTextV2.swift (100%) rename Mail/Views/New Message/{V2 => }/Header Cells/ComposeMessageCellTextFieldV2.swift (100%) rename Mail/Views/New Message/{V2 => }/RecipientFieldV2.swift (100%) rename Mail/Views/New Message/{V2 => }/RecipientsTextFieldV2.swift (100%) diff --git a/Mail/Views/New Message/V2/AutocompletionCell.swift b/Mail/Views/New Message/AutocompletionCell.swift similarity index 100% rename from Mail/Views/New Message/V2/AutocompletionCell.swift rename to Mail/Views/New Message/AutocompletionCell.swift diff --git a/Mail/Views/New Message/V2/AutocompletionViewV2.swift b/Mail/Views/New Message/AutocompletionViewV2.swift similarity index 100% rename from Mail/Views/New Message/V2/AutocompletionViewV2.swift rename to Mail/Views/New Message/AutocompletionViewV2.swift diff --git a/Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/ComposeMessageBodyViewV2.swift similarity index 100% rename from Mail/Views/New Message/V2/ComposeMessageBodyViewV2.swift rename to Mail/Views/New Message/ComposeMessageBodyViewV2.swift diff --git a/Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/ComposeMessageHeaderViewV2.swift similarity index 100% rename from Mail/Views/New Message/V2/ComposeMessageHeaderViewV2.swift rename to Mail/Views/New Message/ComposeMessageHeaderViewV2.swift diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift b/Mail/Views/New Message/ComposeMessageView+Extension.swift similarity index 69% rename from Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift rename to Mail/Views/New Message/ComposeMessageView+Extension.swift index a6b110ed8..02f5d58be 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2+Extension.swift +++ b/Mail/Views/New Message/ComposeMessageView+Extension.swift @@ -22,30 +22,30 @@ import InfomaniakDI import MailCore import RealmSwift -extension ComposeMessageViewV2 { - static func newMessage(mailboxManager: MailboxManager) -> ComposeMessageViewV2 { +extension ComposeMessageView { + static func newMessage(mailboxManager: MailboxManager) -> ComposeMessageView { let draft = Draft(localUUID: UUID().uuidString) - return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) + return ComposeMessageView(draft: draft, mailboxManager: mailboxManager) } - static func replyOrForwardMessage(messageReply: MessageReply, mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + static func replyOrForwardMessage(messageReply: MessageReply, mailboxManager: MailboxManager) -> ComposeMessageView { let draft = Draft.replying(reply: messageReply) - return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager, messageReply: messageReply) + return ComposeMessageView(draft: draft, mailboxManager: mailboxManager, messageReply: messageReply) } - static func edit(draft: Draft, mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + static func edit(draft: Draft, mailboxManager: MailboxManager) -> ComposeMessageView { @InjectService var matomo: MatomoUtils matomo.track(eventWithCategory: .newMessage, name: "openFromDraft") - return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) + return ComposeMessageView(draft: draft, mailboxManager: mailboxManager) } - static func writingTo(recipient: Recipient, mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + static func writingTo(recipient: Recipient, mailboxManager: MailboxManager) -> ComposeMessageView { let draft = Draft.writing(to: recipient) - return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) + return ComposeMessageView(draft: draft, mailboxManager: mailboxManager) } - static func mailTo(urlComponents: URLComponents, mailboxManager: MailboxManager) -> ComposeMessageViewV2 { + static func mailTo(urlComponents: URLComponents, mailboxManager: MailboxManager) -> ComposeMessageView { let draft = Draft.mailTo(urlComponents: urlComponents) - return ComposeMessageViewV2(draft: draft, mailboxManager: mailboxManager) + return ComposeMessageView(draft: draft, mailboxManager: mailboxManager) } } diff --git a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift b/Mail/Views/New Message/ComposeMessageView.swift similarity index 97% rename from Mail/Views/New Message/V2/ComposeMessageViewV2.swift rename to Mail/Views/New Message/ComposeMessageView.swift index e8cef662a..3f67e1c60 100644 --- a/Mail/Views/New Message/V2/ComposeMessageViewV2.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -57,7 +57,7 @@ final class NewMessageAlert: SheetState { } } -struct ComposeMessageViewV2: View { +struct ComposeMessageView: View { @Environment(\.dismiss) private var dismiss @LazyInjectService private var matomo: MatomoUtils @@ -213,8 +213,8 @@ struct ComposeMessageViewV2: View { } } -struct ComposeMessageViewV2_Previews: PreviewProvider { +struct ComposeMessageView_Previews: PreviewProvider { static var previews: some View { - ComposeMessageViewV2.newMessage(mailboxManager: PreviewHelper.sampleMailboxManager) + ComposeMessageView.newMessage(mailboxManager: PreviewHelper.sampleMailboxManager) } } diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift similarity index 100% rename from Mail/Views/New Message/V2/Header Cells/ComposeMessageCellRecipientsV2.swift rename to Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellStaticTextV2.swift similarity index 100% rename from Mail/Views/New Message/V2/Header Cells/ComposeMessageCellStaticTextV2.swift rename to Mail/Views/New Message/Header Cells/ComposeMessageCellStaticTextV2.swift diff --git a/Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellTextFieldV2.swift similarity index 100% rename from Mail/Views/New Message/V2/Header Cells/ComposeMessageCellTextFieldV2.swift rename to Mail/Views/New Message/Header Cells/ComposeMessageCellTextFieldV2.swift diff --git a/Mail/Views/New Message/V2/RecipientFieldV2.swift b/Mail/Views/New Message/RecipientFieldV2.swift similarity index 100% rename from Mail/Views/New Message/V2/RecipientFieldV2.swift rename to Mail/Views/New Message/RecipientFieldV2.swift diff --git a/Mail/Views/New Message/V2/RecipientsTextFieldV2.swift b/Mail/Views/New Message/RecipientsTextFieldV2.swift similarity index 100% rename from Mail/Views/New Message/V2/RecipientsTextFieldV2.swift rename to Mail/Views/New Message/RecipientsTextFieldV2.swift From 175f6b5383332a69d915e5040ff6c85b0e35f349 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 08:51:24 +0200 Subject: [PATCH 29/41] refactor: Rename all views --- Mail/SceneDelegate.swift | 2 +- Mail/Views/Bottom sheets/ContactActionsView.swift | 2 +- ...completionViewV2.swift => AutocompletionView.swift} | 6 +++--- ...geBodyViewV2.swift => ComposeMessageBodyView.swift} | 6 +++--- ...aderViewV2.swift => ComposeMessageHeaderView.swift} | 6 +++--- .../Header Cells/ComposeMessageCellRecipientsV2.swift | 4 ++-- .../{RecipientFieldV2.swift => RecipientField.swift} | 8 ++++---- ...entsTextFieldV2.swift => RecipientsTextField.swift} | 10 +++++----- Mail/Views/SplitView.swift | 2 +- Mail/Views/Thread List/ThreadListView.swift | 2 +- Mail/Views/Thread/MessageHeaderView.swift | 2 +- Mail/Views/ThreadListManagerView.swift | 2 +- 12 files changed, 26 insertions(+), 26 deletions(-) rename Mail/Views/New Message/{AutocompletionViewV2.swift => AutocompletionView.swift} (96%) rename Mail/Views/New Message/{ComposeMessageBodyViewV2.swift => ComposeMessageBodyView.swift} (97%) rename Mail/Views/New Message/{ComposeMessageHeaderViewV2.swift => ComposeMessageHeaderView.swift} (93%) rename Mail/Views/New Message/{RecipientFieldV2.swift => RecipientField.swift} (92%) rename Mail/Views/New Message/{RecipientsTextFieldV2.swift => RecipientsTextField.swift} (91%) diff --git a/Mail/SceneDelegate.swift b/Mail/SceneDelegate.swift index 4b3cae583..110d2ef8e 100644 --- a/Mail/SceneDelegate.swift +++ b/Mail/SceneDelegate.swift @@ -184,7 +184,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, AccountManagerDelegate } if Constants.isMailTo(url) { - let newMessageView = ComposeMessageViewV2.mailTo(urlComponents: urlComponents, mailboxManager: mailboxManager) + let newMessageView = ComposeMessageView.mailTo(urlComponents: urlComponents, mailboxManager: mailboxManager) let viewController = UIHostingController(rootView: newMessageView) window?.rootViewController?.present(viewController, animated: true) } diff --git a/Mail/Views/Bottom sheets/ContactActionsView.swift b/Mail/Views/Bottom sheets/ContactActionsView.swift index 59b159d3f..837b537e4 100644 --- a/Mail/Views/Bottom sheets/ContactActionsView.swift +++ b/Mail/Views/Bottom sheets/ContactActionsView.swift @@ -99,7 +99,7 @@ struct ContactActionsView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 24) .sheet(item: $writtenToRecipient) { writtenToRecipient in - ComposeMessageViewV2.writingTo(recipient: writtenToRecipient, mailboxManager: mailboxManager) + ComposeMessageView.writingTo(recipient: writtenToRecipient, mailboxManager: mailboxManager) } .matomoView(view: [MatomoUtils.View.bottomSheet.displayName, "ContactActionsView"]) } diff --git a/Mail/Views/New Message/AutocompletionViewV2.swift b/Mail/Views/New Message/AutocompletionView.swift similarity index 96% rename from Mail/Views/New Message/AutocompletionViewV2.swift rename to Mail/Views/New Message/AutocompletionView.swift index 4937fc5d0..5b92be260 100644 --- a/Mail/Views/New Message/AutocompletionViewV2.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -20,7 +20,7 @@ import MailCore import RealmSwift import SwiftUI -struct AutocompletionViewV2: View { +struct AutocompletionView: View { @State private var shouldAddUserProposal = false @Binding var autocompletion: [Recipient] @@ -76,9 +76,9 @@ struct AutocompletionViewV2: View { } } -struct AutocompletionViewV2_Previews: PreviewProvider { +struct AutocompletionView_Previews: PreviewProvider { static var previews: some View { - AutocompletionViewV2( + AutocompletionView( autocompletion: .constant([]), currentSearch: .constant(""), addedRecipients: .constant([PreviewHelper.sampleRecipient1].toRealmList()) diff --git a/Mail/Views/New Message/ComposeMessageBodyViewV2.swift b/Mail/Views/New Message/ComposeMessageBodyView.swift similarity index 97% rename from Mail/Views/New Message/ComposeMessageBodyViewV2.swift rename to Mail/Views/New Message/ComposeMessageBodyView.swift index 18c03328a..371c2ce2c 100644 --- a/Mail/Views/New Message/ComposeMessageBodyViewV2.swift +++ b/Mail/Views/New Message/ComposeMessageBodyView.swift @@ -21,7 +21,7 @@ import MailCore import RealmSwift import SwiftUI -struct ComposeMessageBodyViewV2: View { +struct ComposeMessageBodyView: View { @Environment(\.dismiss) private var dismiss @EnvironmentObject private var mailboxManager: MailboxManager @@ -165,9 +165,9 @@ struct ComposeMessageBodyViewV2: View { } } -struct ComposeMessageBodyViewV2_Previews: PreviewProvider { +struct ComposeMessageBodyView_Previews: PreviewProvider { static var previews: some View { - ComposeMessageBodyViewV2( + ComposeMessageBodyView( draft: Draft(), isLoadingContent: .constant(false), editorFocus: .constant(false), diff --git a/Mail/Views/New Message/ComposeMessageHeaderViewV2.swift b/Mail/Views/New Message/ComposeMessageHeaderView.swift similarity index 93% rename from Mail/Views/New Message/ComposeMessageHeaderViewV2.swift rename to Mail/Views/New Message/ComposeMessageHeaderView.swift index de911fb00..a5dca4eb3 100644 --- a/Mail/Views/New Message/ComposeMessageHeaderViewV2.swift +++ b/Mail/Views/New Message/ComposeMessageHeaderView.swift @@ -20,7 +20,7 @@ import MailCore import RealmSwift import SwiftUI -struct ComposeMessageHeaderViewV2: View { +struct ComposeMessageHeaderView: View { @EnvironmentObject private var mailboxManager: MailboxManager @State private var showRecipientsFields = false @@ -79,8 +79,8 @@ struct ComposeMessageHeaderViewV2: View { } } -struct ComposeMessageHeaderViewV2_Previews: PreviewProvider { +struct ComposeMessageHeaderView_Previews: PreviewProvider { static var previews: some View { - ComposeMessageHeaderViewV2(draft: Draft(), autocompletionType: .constant(nil)) + ComposeMessageHeaderView(draft: Draft(), autocompletionType: .constant(nil)) } } diff --git a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift index e71fe5f55..7d3ed4191 100644 --- a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift @@ -42,7 +42,7 @@ struct ComposeMessageCellRecipientsV2: View { Text(type.title) .textStyle(.bodySecondary) - RecipientFieldV2(currentText: $currentText, recipients: $recipients, focusedField: _focusedField, type: type) { + RecipientField(currentText: $currentText, recipients: $recipients, focusedField: _focusedField, type: type) { if let bestMatch = autocompletion.first { addNewRecipient(bestMatch) } @@ -58,7 +58,7 @@ struct ComposeMessageCellRecipientsV2: View { } if autocompletionType == type { - AutocompletionViewV2( + AutocompletionView( autocompletion: $autocompletion, currentSearch: $currentText, addedRecipients: $recipients, diff --git a/Mail/Views/New Message/RecipientFieldV2.swift b/Mail/Views/New Message/RecipientField.swift similarity index 92% rename from Mail/Views/New Message/RecipientFieldV2.swift rename to Mail/Views/New Message/RecipientField.swift index 8d694ad8e..3e66628c6 100644 --- a/Mail/Views/New Message/RecipientFieldV2.swift +++ b/Mail/Views/New Message/RecipientField.swift @@ -35,7 +35,7 @@ extension VerticalAlignment { static let newMessageCellAlignment = VerticalAlignment(NewMessageCellAlignment.self) } -struct RecipientFieldV2: View { +struct RecipientField: View { @State private var keyboardHeight: CGFloat = 0 @Binding var currentText: String @@ -65,7 +65,7 @@ struct RecipientFieldV2: View { .alignmentGuide(.newMessageCellAlignment) { d in d[.top] + 21 } } - RecipientsTextFieldV2View(text: $currentText, onSubmit: onSubmit, onBackspace: handleBackspaceTextField) + RecipientsTextField(text: $currentText, onSubmit: onSubmit, onBackspace: handleBackspaceTextField) .focused($focusedField, equals: type) } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { output in @@ -102,9 +102,9 @@ struct RecipientFieldV2: View { } } -struct RecipientFieldV2_Previews: PreviewProvider { +struct RecipientField_Previews: PreviewProvider { static var previews: some View { - RecipientFieldV2(currentText: .constant(""), recipients: .constant([ + RecipientField(currentText: .constant(""), recipients: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 ].toRealmList()), type: .to) } diff --git a/Mail/Views/New Message/RecipientsTextFieldV2.swift b/Mail/Views/New Message/RecipientsTextField.swift similarity index 91% rename from Mail/Views/New Message/RecipientsTextFieldV2.swift rename to Mail/Views/New Message/RecipientsTextField.swift index f31b84f90..85b20c0ba 100644 --- a/Mail/Views/New Message/RecipientsTextFieldV2.swift +++ b/Mail/Views/New Message/RecipientsTextField.swift @@ -19,14 +19,14 @@ import SwiftUI import UIKit -struct RecipientsTextFieldV2View: UIViewRepresentable { +struct RecipientsTextField: UIViewRepresentable { @Binding var text: String var onSubmit: (() -> Void)? let onBackspace: (Bool) -> Void func makeUIView(context: Context) -> UITextField { - let textField = RecipientsTextField() + let textField = UIRecipientsTextField() textField.delegate = context.coordinator textField.addTarget(context.coordinator, action: #selector(context.coordinator.textDidChanged(_:)), for: .editingChanged) textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) @@ -45,9 +45,9 @@ struct RecipientsTextFieldV2View: UIViewRepresentable { } class Coordinator: NSObject, UITextFieldDelegate { - let parent: RecipientsTextFieldV2View + let parent: RecipientsTextField - init(_ parent: RecipientsTextFieldV2View) { + init(_ parent: RecipientsTextField) { self.parent = parent } @@ -70,7 +70,7 @@ struct RecipientsTextFieldV2View: UIViewRepresentable { /* * We need to create our own UITextField to benefit from the `deleteBackward()` function */ -class RecipientsTextField: UITextField { +class UIRecipientsTextField: UITextField { var onBackspace: ((Bool) -> Void)? override init(frame: CGRect) { diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index f2749a9b4..04b644c97 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -91,7 +91,7 @@ struct SplitView: View { } } .sheet(item: $navigationStore.messageReply) { messageReply in - ComposeMessageViewV2.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) + ComposeMessageView.replyOrForwardMessage(messageReply: messageReply, mailboxManager: mailboxManager) } .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in Task { diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 4c336bd0c..ba207ce29 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -245,7 +245,7 @@ struct ThreadListView: View { } } .sheet(isPresented: $isShowingComposeNewMessageView) { - ComposeMessageViewV2.newMessage(mailboxManager: viewModel.mailboxManager) + ComposeMessageView.newMessage(mailboxManager: viewModel.mailboxManager) } .customAlert(item: $flushAlert) { item in FlushFolderAlertView(flushAlert: item, folder: viewModel.folder) diff --git a/Mail/Views/Thread/MessageHeaderView.swift b/Mail/Views/Thread/MessageHeaderView.swift index a0d8612d3..e1d2dc553 100644 --- a/Mail/Views/Thread/MessageHeaderView.swift +++ b/Mail/Views/Thread/MessageHeaderView.swift @@ -61,7 +61,7 @@ struct MessageHeaderView: View { } } .sheet(item: $editedDraft) { editedDraft in - ComposeMessageViewV2.edit(draft: editedDraft, mailboxManager: mailboxManager) + ComposeMessageView.edit(draft: editedDraft, mailboxManager: mailboxManager) } } diff --git a/Mail/Views/ThreadListManagerView.swift b/Mail/Views/ThreadListManagerView.swift index cb4acc998..54c18eced 100644 --- a/Mail/Views/ThreadListManagerView.swift +++ b/Mail/Views/ThreadListManagerView.swift @@ -54,7 +54,7 @@ struct ThreadListManagerView: View { } .animation(.easeInOut(duration: 0.25), value: splitViewManager.showSearch) .sheet(item: $editedMessageDraft) { draft in - ComposeMessageViewV2.edit(draft: draft, mailboxManager: mailboxManager) + ComposeMessageView.edit(draft: draft, mailboxManager: mailboxManager) } } } From 49e36daeb498a1af0326a40cd6f2632859069d70 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 09:02:14 +0200 Subject: [PATCH 30/41] refactor: Clean code --- Mail/Views/New Message/AutocompletionCell.swift | 1 - Mail/Views/New Message/ComposeMessageView.swift | 4 ++-- MailCore/Models/Draft.swift | 12 ------------ 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/Mail/Views/New Message/AutocompletionCell.swift b/Mail/Views/New Message/AutocompletionCell.swift index a1ce0dc8c..c65db05d8 100644 --- a/Mail/Views/New Message/AutocompletionCell.swift +++ b/Mail/Views/New Message/AutocompletionCell.swift @@ -1,4 +1,3 @@ -// /* Infomaniak Mail - iOS App Copyright (C) 2022 Infomaniak Network SA diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift index 3f67e1c60..1aa2646ad 100644 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -102,10 +102,10 @@ struct ComposeMessageView: View { NavigationView { ScrollView { VStack(spacing: 0) { - ComposeMessageHeaderViewV2(draft: draft, focusedField: _focusedField, autocompletionType: $autocompletionType) + ComposeMessageHeaderView(draft: draft, focusedField: _focusedField, autocompletionType: $autocompletionType) if autocompletionType == nil { - ComposeMessageBodyViewV2( + ComposeMessageBodyView( draft: draft, isLoadingContent: $isLoadingContent, editorFocus: $editorFocus, diff --git a/MailCore/Models/Draft.swift b/MailCore/Models/Draft.swift index 683aff923..766e06d45 100644 --- a/MailCore/Models/Draft.swift +++ b/MailCore/Models/Draft.swift @@ -195,18 +195,6 @@ public class Draft: Object, Codable, Identifiable { self.action = action } - public static func mailTo(subject: String?, - body: String?, - to: [Recipient], - cc: [Recipient], - bcc: [Recipient]) -> Draft { - return Draft(subject: subject ?? "", - body: body ?? "", - to: to, - cc: cc, - bcc: bcc) - } - public static func mailTo(urlComponents: URLComponents) -> Draft { let subject = urlComponents.getQueryItem(named: "subject") let body = urlComponents.getQueryItem(named: "body")? From adf203ce482cdc05f4ff00fdd49ba419dada7912 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 11:38:02 +0200 Subject: [PATCH 31/41] fix: Update some wrong behaviors --- Mail/Views/New Message/AutocompletionView.swift | 3 +++ Mail/Views/New Message/ComposeMessageBodyView.swift | 5 ++++- Mail/Views/New Message/ComposeMessageView.swift | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Mail/Views/New Message/AutocompletionView.swift b/Mail/Views/New Message/AutocompletionView.swift index 5b92be260..69229d86f 100644 --- a/Mail/Views/New Message/AutocompletionView.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -51,6 +51,9 @@ struct AutocompletionView: View { ) } } + .onAppear { + updateAutocompletion(currentSearch) + } .onChange(of: currentSearch, perform: updateAutocompletion) } diff --git a/Mail/Views/New Message/ComposeMessageBodyView.swift b/Mail/Views/New Message/ComposeMessageBodyView.swift index 371c2ce2c..7ea4c8cd6 100644 --- a/Mail/Views/New Message/ComposeMessageBodyView.swift +++ b/Mail/Views/New Message/ComposeMessageBodyView.swift @@ -22,7 +22,6 @@ import RealmSwift import SwiftUI struct ComposeMessageBodyView: View { - @Environment(\.dismiss) private var dismiss @EnvironmentObject private var mailboxManager: MailboxManager @State private var isShowingCamera = false @@ -39,6 +38,7 @@ struct ComposeMessageBodyView: View { @ObservedObject var attachmentsManager: AttachmentsManager @ObservedObject var alert: NewMessageAlert + let dismiss: DismissAction let messageReply: MessageReply? private var isRemoteContentBlocked: Bool { @@ -167,12 +167,15 @@ struct ComposeMessageBodyView: View { struct ComposeMessageBodyView_Previews: PreviewProvider { static var previews: some View { + @Environment(\.dismiss) var dismiss + ComposeMessageBodyView( draft: Draft(), isLoadingContent: .constant(false), editorFocus: .constant(false), attachmentsManager: AttachmentsManager(draft: Draft(), mailboxManager: PreviewHelper.sampleMailboxManager), alert: NewMessageAlert(), + dismiss: dismiss, messageReply: nil ) } diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift index 1aa2646ad..7805ce431 100644 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -111,6 +111,7 @@ struct ComposeMessageView: View { editorFocus: $editorFocus, attachmentsManager: attachmentsManager, alert: alert, + dismiss: dismiss, messageReply: messageReply ) } From 1825827fcd1a97292b19f33d9db115230ef1edb8 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 14:47:54 +0200 Subject: [PATCH 32/41] feat: Update autocompletion cells --- Mail/Components/AvatarView.swift | 5 +- Mail/Components/RecipientCell.swift | 5 +- Mail/Components/UnknownRecipientView.swift | 49 +++++++++++++++++++ .../New Message/AutocompletionCell.swift | 14 ++++-- .../New Message/AutocompletionView.swift | 29 ++++++----- .../ComposeMessageHeaderView.swift | 10 ++-- ...ift => ComposeMessageCellRecipients.swift} | 10 ++-- ...ift => ComposeMessageCellStaticText.swift} | 6 +-- ...wift => ComposeMessageCellTextField.swift} | 6 +-- MailCore/Models/Recipient.swift | 4 ++ MailCore/UI/UIConstants.swift | 4 ++ 11 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 Mail/Components/UnknownRecipientView.swift rename Mail/Views/New Message/Header Cells/{ComposeMessageCellRecipientsV2.swift => ComposeMessageCellRecipients.swift} (91%) rename Mail/Views/New Message/Header Cells/{ComposeMessageCellStaticTextV2.swift => ComposeMessageCellStaticText.swift} (85%) rename Mail/Views/New Message/Header Cells/{ComposeMessageCellTextFieldV2.swift => ComposeMessageCellTextField.swift} (87%) diff --git a/Mail/Components/AvatarView.swift b/Mail/Components/AvatarView.swift index 88e1846c5..2dcc8f2af 100644 --- a/Mail/Components/AvatarView.swift +++ b/Mail/Components/AvatarView.swift @@ -25,10 +25,13 @@ import SwiftUI struct AvatarView: View { let avatarDisplayable: AvatarDisplayable var size: CGFloat = 28 + var unknownAvatar = false var body: some View { Group { - if let avatarImageRequest = avatarDisplayable.avatarImageRequest { + if unknownAvatar { + UnknownRecipientView(size: size) + } else if let avatarImageRequest = avatarDisplayable.avatarImageRequest { LazyImage(request: avatarImageRequest) { state in if let image = state.image { ContactImage(image: image, size: size) diff --git a/Mail/Components/RecipientCell.swift b/Mail/Components/RecipientCell.swift index 478039e12..d216786a2 100644 --- a/Mail/Components/RecipientCell.swift +++ b/Mail/Components/RecipientCell.swift @@ -22,10 +22,11 @@ import SwiftUI struct RecipientCell: View { let recipient: Recipient var highlight: String? + var unknownRecipient = false var body: some View { HStack(spacing: 8) { - AvatarView(avatarDisplayable: recipient, size: 40) + AvatarView(avatarDisplayable: recipient, size: 40, unknownAvatar: unknownRecipient) .accessibilityHidden(true) if recipient.name.isEmpty { @@ -48,7 +49,7 @@ struct RecipientCell: View { private func highlightedAttributedString(from data: String) -> AttributedString { var attributedString = AttributedString(data) - guard let highlight else { return attributedString } + guard let highlight, !unknownRecipient else { return attributedString } if let range = attributedString.range(of: highlight, options: .caseInsensitive) { attributedString[range].foregroundColor = .accentColor diff --git a/Mail/Components/UnknownRecipientView.swift b/Mail/Components/UnknownRecipientView.swift new file mode 100644 index 000000000..613cf4e5d --- /dev/null +++ b/Mail/Components/UnknownRecipientView.swift @@ -0,0 +1,49 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import MailCore +import MailResources +import SwiftUI + +struct UnknownRecipientView: View { + let size: CGFloat + + static let imagePadding: CGFloat = 8 + + private var iconSize: CGFloat { + return size - 2 * UIConstants.unknownRecipientHorizontalPadding + } + + var body: some View { + Circle() + .fill(Color.accentColor) + .frame(width: size, height: size) + .overlay { + MailResourcesAsset.userBold.swiftUIImage + .resizable() + .foregroundColor(MailResourcesAsset.backgroundColor.swiftUIColor) + .frame(width: iconSize, height: iconSize) + } + } +} + +struct UnknownRecipientView_Previews: PreviewProvider { + static var previews: some View { + UnknownRecipientView(size: 40) + } +} diff --git a/Mail/Views/New Message/AutocompletionCell.swift b/Mail/Views/New Message/AutocompletionCell.swift index c65db05d8..61e2e64f8 100644 --- a/Mail/Views/New Message/AutocompletionCell.swift +++ b/Mail/Views/New Message/AutocompletionCell.swift @@ -25,18 +25,19 @@ struct AutocompletionCell: View { let recipient: Recipient var highlight: String? let alreadyAppend: Bool + let unknownRecipient: Bool var body: some View { HStack(spacing: 12) { Button { addRecipient(recipient) } label: { - RecipientCell(recipient: recipient, highlight: highlight) + RecipientCell(recipient: recipient, highlight: highlight, unknownRecipient: unknownRecipient) } .allowsHitTesting(!alreadyAppend) - .opacity(alreadyAppend ? 0.5 : 1) + .opacity(alreadyAppend && !unknownRecipient ? 0.5 : 1) - if alreadyAppend { + if alreadyAppend && !unknownRecipient { MailResourcesAsset.checked.swiftUIImage .resizable() .frame(width: 24, height: 24) @@ -48,6 +49,11 @@ struct AutocompletionCell: View { struct AutocompletionCell_Previews: PreviewProvider { static var previews: some View { - AutocompletionCell(addRecipient: { _ in /* Preview */ }, recipient: PreviewHelper.sampleRecipient1, alreadyAppend: false) + AutocompletionCell( + addRecipient: { _ in /* Preview */ }, + recipient: PreviewHelper.sampleRecipient1, + alreadyAppend: false, + unknownRecipient: false + ) } } diff --git a/Mail/Views/New Message/AutocompletionView.swift b/Mail/Views/New Message/AutocompletionView.swift index 69229d86f..86bffb21b 100644 --- a/Mail/Views/New Message/AutocompletionView.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -30,25 +30,24 @@ struct AutocompletionView: View { let addRecipient: @MainActor (Recipient) -> Void var body: some View { - LazyVStack { + LazyVStack(spacing: UIConstants.autocompletionVerticalPadding) { ForEach(autocompletion) { recipient in - VStack(alignment: .leading, spacing: 8) { + let isLastRecipient = autocompletion.last?.isSameRecipient(as: recipient) == true + let isUserProposal = shouldAddUserProposal && isLastRecipient + + VStack(alignment: .leading, spacing: UIConstants.autocompletionVerticalPadding) { AutocompletionCell( addRecipient: addRecipient, recipient: recipient, highlight: currentSearch, - alreadyAppend: addedRecipients.contains { $0.email == recipient.email && $0.name == recipient.name } + alreadyAppend: addedRecipients.contains { $0.isSameRecipient(as: recipient) }, + unknownRecipient: isUserProposal ) - IKDivider() - } - } - if shouldAddUserProposal { - AutocompletionCell( - addRecipient: addRecipient, - recipient: Recipient(email: currentSearch, name: ""), - alreadyAppend: false - ) + if !isLastRecipient { + IKDivider() + } + } } } .onAppear { @@ -73,8 +72,12 @@ struct AutocompletionView: View { let realResults = autocompleteRecipients.filter { !addedRecipients.map(\.email).contains($0.email) } withAnimation { - autocompletion = autocompleteRecipients shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == currentSearch) + if shouldAddUserProposal { + autocompleteRecipients.append(Recipient(email: currentSearch, name: "")) + } + + autocompletion = autocompleteRecipients } } } diff --git a/Mail/Views/New Message/ComposeMessageHeaderView.swift b/Mail/Views/New Message/ComposeMessageHeaderView.swift index a5dca4eb3..0fcc92366 100644 --- a/Mail/Views/New Message/ComposeMessageHeaderView.swift +++ b/Mail/Views/New Message/ComposeMessageHeaderView.swift @@ -33,13 +33,13 @@ struct ComposeMessageHeaderView: View { var body: some View { VStack(spacing: UIConstants.composeViewVerticalSpacing) { - ComposeMessageCellStaticTextV2( + ComposeMessageCellStaticText( autocompletionType: $autocompletionType, type: .from, text: mailboxManager.mailbox.email ) - ComposeMessageCellRecipientsV2( + ComposeMessageCellRecipients( recipients: $draft.to, showRecipientsFields: $showRecipientsFields, autocompletionType: $autocompletionType, @@ -48,7 +48,7 @@ struct ComposeMessageHeaderView: View { ) if showRecipientsFields { - ComposeMessageCellRecipientsV2( + ComposeMessageCellRecipients( recipients: $draft.cc, showRecipientsFields: $showRecipientsFields, autocompletionType: $autocompletionType, @@ -56,7 +56,7 @@ struct ComposeMessageHeaderView: View { type: .cc ) - ComposeMessageCellRecipientsV2( + ComposeMessageCellRecipients( recipients: $draft.bcc, showRecipientsFields: $showRecipientsFields, autocompletionType: $autocompletionType, @@ -65,7 +65,7 @@ struct ComposeMessageHeaderView: View { ) } - ComposeMessageCellTextFieldV2( + ComposeMessageCellTextField( text: $draft.subject, autocompletionType: $autocompletionType, focusedField: _focusedField, diff --git a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift similarity index 91% rename from Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift rename to Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift index 7d3ed4191..938a641fc 100644 --- a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipientsV2.swift +++ b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift @@ -23,7 +23,7 @@ import MailResources import RealmSwift import SwiftUI -struct ComposeMessageCellRecipientsV2: View { +struct ComposeMessageCellRecipients: View { @State private var currentText = "" @State private var autocompletion = [Recipient]() @@ -36,7 +36,7 @@ struct ComposeMessageCellRecipientsV2: View { let type: ComposeViewFieldType var body: some View { - VStack(spacing: UIConstants.composeViewVerticalSpacing) { + VStack(spacing: 0) { if autocompletionType == nil || autocompletionType == type { HStack { Text(type.title) @@ -55,6 +55,7 @@ struct ComposeMessageCellRecipientsV2: View { } .frame(maxWidth: .infinity, alignment: .leading) IKDivider() + .padding(.top, UIConstants.composeViewVerticalSpacing) } if autocompletionType == type { @@ -64,6 +65,7 @@ struct ComposeMessageCellRecipientsV2: View { addedRecipients: $recipients, addRecipient: addNewRecipient ) + .padding(.top, 8) } } .onTapGesture { @@ -96,9 +98,9 @@ struct ComposeMessageCellRecipientsV2: View { } } -struct ComposeMessageCellRecipientsV2_Previews: PreviewProvider { +struct ComposeMessageCellRecipients_Previews: PreviewProvider { static var previews: some View { - ComposeMessageCellRecipientsV2(recipients: .constant([ + ComposeMessageCellRecipients(recipients: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 ].toRealmList()), showRecipientsFields: .constant(false), autocompletionType: .constant(nil), type: .bcc) } diff --git a/Mail/Views/New Message/Header Cells/ComposeMessageCellStaticTextV2.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellStaticText.swift similarity index 85% rename from Mail/Views/New Message/Header Cells/ComposeMessageCellStaticTextV2.swift rename to Mail/Views/New Message/Header Cells/ComposeMessageCellStaticText.swift index 69af623ca..aaf27c89e 100644 --- a/Mail/Views/New Message/Header Cells/ComposeMessageCellStaticTextV2.swift +++ b/Mail/Views/New Message/Header Cells/ComposeMessageCellStaticText.swift @@ -19,7 +19,7 @@ import MailCore import SwiftUI -struct ComposeMessageCellStaticTextV2: View { +struct ComposeMessageCellStaticText: View { @Binding var autocompletionType: ComposeViewFieldType? let type: ComposeViewFieldType @@ -42,8 +42,8 @@ struct ComposeMessageCellStaticTextV2: View { } } -struct ComposeMessageStaticTextV2_Previews: PreviewProvider { +struct ComposeMessageStaticText_Previews: PreviewProvider { static var previews: some View { - ComposeMessageCellStaticTextV2(autocompletionType: .constant(nil), type: .from, text: "myaddress@email.com") + ComposeMessageCellStaticText(autocompletionType: .constant(nil), type: .from, text: "myaddress@email.com") } } diff --git a/Mail/Views/New Message/Header Cells/ComposeMessageCellTextFieldV2.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellTextField.swift similarity index 87% rename from Mail/Views/New Message/Header Cells/ComposeMessageCellTextFieldV2.swift rename to Mail/Views/New Message/Header Cells/ComposeMessageCellTextField.swift index 07cfaf4a4..85bc51caa 100644 --- a/Mail/Views/New Message/Header Cells/ComposeMessageCellTextFieldV2.swift +++ b/Mail/Views/New Message/Header Cells/ComposeMessageCellTextField.swift @@ -19,7 +19,7 @@ import MailCore import SwiftUI -struct ComposeMessageCellTextFieldV2: View { +struct ComposeMessageCellTextField: View { @Binding var text: String @Binding var autocompletionType: ComposeViewFieldType? @@ -47,8 +47,8 @@ struct ComposeMessageCellTextFieldV2: View { } } -struct ComposeMessageCellTextFieldV2_Previews: PreviewProvider { +struct ComposeMessageCellTextField_Previews: PreviewProvider { static var previews: some View { - ComposeMessageCellTextFieldV2(text: .constant(""), autocompletionType: .constant(nil), type: .subject) + ComposeMessageCellTextField(text: .constant(""), autocompletionType: .constant(nil), type: .subject) } } diff --git a/MailCore/Models/Recipient.swift b/MailCore/Models/Recipient.swift index b50518a35..fd1bc0f57 100644 --- a/MailCore/Models/Recipient.swift +++ b/MailCore/Models/Recipient.swift @@ -125,6 +125,10 @@ public class Recipient: EmbeddedObject, Codable { return "\(name) \(emailString)" } } + + public func isSameRecipient(as recipient: Recipient) -> Bool { + return email == recipient.email && name == recipient.name + } } extension Recipient: AvatarDisplayable { diff --git a/MailCore/UI/UIConstants.swift b/MailCore/UI/UIConstants.swift index f2ebd3ac2..846d1e3d4 100644 --- a/MailCore/UI/UIConstants.swift +++ b/MailCore/UI/UIConstants.swift @@ -109,6 +109,10 @@ public enum UIConstants { public static let bottomSheetHorizontalPadding: CGFloat = 24 + public static let unknownRecipientHorizontalPadding: CGFloat = 8 + + public static let autocompletionVerticalPadding: CGFloat = 8 + public static let componentsMaxWidth: CGFloat = 496 public static let chipInsets = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8) From f471691a7c064117cbdd988352dfd4666ced4863 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 15:02:15 +0200 Subject: [PATCH 33/41] feat: Add unknown recipient title --- Mail/Views/New Message/AutocompletionView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mail/Views/New Message/AutocompletionView.swift b/Mail/Views/New Message/AutocompletionView.swift index 86bffb21b..61265273f 100644 --- a/Mail/Views/New Message/AutocompletionView.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -17,6 +17,7 @@ */ import MailCore +import MailResources import RealmSwift import SwiftUI @@ -74,7 +75,8 @@ struct AutocompletionView: View { withAnimation { shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == currentSearch) if shouldAddUserProposal { - autocompleteRecipients.append(Recipient(email: currentSearch, name: "")) + autocompleteRecipients + .append(Recipient(email: currentSearch, name: MailResourcesStrings.Localizable.addUnknownRecipientTitle)) } autocompletion = autocompleteRecipients From d0c8509ad7249b6a0f1d0b134fd46f893c2de389 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 15:14:26 +0200 Subject: [PATCH 34/41] feat: Check if email is already used --- .../ComposeMessageCellRecipients.swift | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift index 938a641fc..f85cc81e2 100644 --- a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift +++ b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift @@ -86,15 +86,20 @@ struct ComposeMessageCellRecipients: View { @InjectService var matomo: MatomoUtils matomo.track(eventWithCategory: .newMessage, name: "addNewRecipient") - if Constants.isEmailAddress(recipient.email) { - withAnimation { - $recipients.append(recipient) - } - currentText = "" - } else { - IKSnackBar - .showSnackBar(message: MailResourcesStrings.Localizable.addUnknownRecipientInvalidEmail) + guard Constants.isEmailAddress(recipient.email) else { + IKSnackBar.showSnackBar(message: MailResourcesStrings.Localizable.addUnknownRecipientInvalidEmail) + return + } + + guard !recipients.contains(where: { $0.isSameRecipient(as: recipient) }) else { + IKSnackBar.showSnackBar(message: MailResourcesStrings.Localizable.addUnknownRecipientAlreadyUsed) + return + } + + withAnimation { + $recipients.append(recipient) } + currentText = "" } } From c8e2dfe6692209e3eb94bb27b7d676d541af9de7 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 15:37:25 +0200 Subject: [PATCH 35/41] fix: Already append unknown recipient --- Mail/Components/AvatarView.swift | 5 +- Mail/Components/RecipientCell.swift | 26 ++++++++--- .../New Message/AutocompletionCell.swift | 8 +++- .../New Message/AutocompletionView.swift | 2 +- .../New Message/UnknownRecipientCell.swift | 46 +++++++++++++++++++ 5 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 Mail/Views/New Message/UnknownRecipientCell.swift diff --git a/Mail/Components/AvatarView.swift b/Mail/Components/AvatarView.swift index 2dcc8f2af..88e1846c5 100644 --- a/Mail/Components/AvatarView.swift +++ b/Mail/Components/AvatarView.swift @@ -25,13 +25,10 @@ import SwiftUI struct AvatarView: View { let avatarDisplayable: AvatarDisplayable var size: CGFloat = 28 - var unknownAvatar = false var body: some View { Group { - if unknownAvatar { - UnknownRecipientView(size: size) - } else if let avatarImageRequest = avatarDisplayable.avatarImageRequest { + if let avatarImageRequest = avatarDisplayable.avatarImageRequest { LazyImage(request: avatarImageRequest) { state in if let image = state.image { ContactImage(image: image, size: size) diff --git a/Mail/Components/RecipientCell.swift b/Mail/Components/RecipientCell.swift index d216786a2..c54d4dc12 100644 --- a/Mail/Components/RecipientCell.swift +++ b/Mail/Components/RecipientCell.swift @@ -19,14 +19,29 @@ import MailCore import SwiftUI +struct RecipientCellModifier: ViewModifier { + func body(content: Content) -> some View { + content + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .leading) + .accessibilityElement(children: .combine) + .accessibilityAddTraits(.isButton) + } +} + +extension View { + func recipientCellModifier() -> some View { + modifier(RecipientCellModifier()) + } +} + struct RecipientCell: View { let recipient: Recipient var highlight: String? - var unknownRecipient = false var body: some View { HStack(spacing: 8) { - AvatarView(avatarDisplayable: recipient, size: 40, unknownAvatar: unknownRecipient) + AvatarView(avatarDisplayable: recipient, size: 40) .accessibilityHidden(true) if recipient.name.isEmpty { @@ -41,15 +56,12 @@ struct RecipientCell: View { } } } - .lineLimit(1) - .frame(maxWidth: .infinity, alignment: .leading) - .accessibilityElement(children: .combine) - .accessibilityAddTraits(.isButton) + .recipientCellModifier() } private func highlightedAttributedString(from data: String) -> AttributedString { var attributedString = AttributedString(data) - guard let highlight, !unknownRecipient else { return attributedString } + guard let highlight else { return attributedString } if let range = attributedString.range(of: highlight, options: .caseInsensitive) { attributedString[range].foregroundColor = .accentColor diff --git a/Mail/Views/New Message/AutocompletionCell.swift b/Mail/Views/New Message/AutocompletionCell.swift index 61e2e64f8..d1f6d78b7 100644 --- a/Mail/Views/New Message/AutocompletionCell.swift +++ b/Mail/Views/New Message/AutocompletionCell.swift @@ -32,9 +32,13 @@ struct AutocompletionCell: View { Button { addRecipient(recipient) } label: { - RecipientCell(recipient: recipient, highlight: highlight, unknownRecipient: unknownRecipient) + if unknownRecipient { + UnknownRecipientCell(recipient: recipient) + } else { + RecipientCell(recipient: recipient, highlight: highlight) + } } - .allowsHitTesting(!alreadyAppend) + .allowsHitTesting(!alreadyAppend || unknownRecipient) .opacity(alreadyAppend && !unknownRecipient ? 0.5 : 1) if alreadyAppend && !unknownRecipient { diff --git a/Mail/Views/New Message/AutocompletionView.swift b/Mail/Views/New Message/AutocompletionView.swift index 61265273f..37abdf388 100644 --- a/Mail/Views/New Message/AutocompletionView.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -76,7 +76,7 @@ struct AutocompletionView: View { shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == currentSearch) if shouldAddUserProposal { autocompleteRecipients - .append(Recipient(email: currentSearch, name: MailResourcesStrings.Localizable.addUnknownRecipientTitle)) + .append(Recipient(email: currentSearch, name: "")) } autocompletion = autocompleteRecipients diff --git a/Mail/Views/New Message/UnknownRecipientCell.swift b/Mail/Views/New Message/UnknownRecipientCell.swift new file mode 100644 index 000000000..acb3a05d8 --- /dev/null +++ b/Mail/Views/New Message/UnknownRecipientCell.swift @@ -0,0 +1,46 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import MailCore +import MailResources +import SwiftUI + +struct UnknownRecipientCell: View { + let recipient: Recipient + + var body: some View { + HStack(spacing: 8) { + UnknownRecipientView(size: 40) + .accessibilityHidden(true) + + VStack(alignment: .leading) { + Text(MailResourcesStrings.Localizable.addUnknownRecipientTitle) + .textStyle(.bodyMedium) + Text(recipient.email) + .textStyle(.bodySecondary) + } + } + .recipientCellModifier() + } +} + +struct UnknownRecipientCell_Previews: PreviewProvider { + static var previews: some View { + UnknownRecipientCell(recipient: PreviewHelper.sampleRecipient1) + } +} From e92070d61a8796c0ae628895b60d18bff554959f Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 19 Jun 2023 16:01:18 +0200 Subject: [PATCH 36/41] chore: Update localization --- .../Localizable/de.lproj/Localizable.strings | Bin 74122 -> 69078 bytes .../Localizable/en.lproj/Localizable.strings | Bin 70160 -> 65398 bytes .../Localizable/es.lproj/Localizable.strings | Bin 74114 -> 69014 bytes .../Localizable/fr.lproj/Localizable.strings | Bin 74682 -> 69474 bytes .../Localizable/it.lproj/Localizable.strings | Bin 73882 -> 68866 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/MailResources/Localizable/de.lproj/Localizable.strings b/MailResources/Localizable/de.lproj/Localizable.strings index ec1051dfb8b9419b6c125e1c0974fd4716a0aab2..159944cf31ba4d044c25f67b25634a7d0c903e88 100644 GIT binary patch delta 2170 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 diff --git a/MailResources/Localizable/en.lproj/Localizable.strings b/MailResources/Localizable/en.lproj/Localizable.strings index 01a14507397836c974a30143179d0df4cd847296..9e952252bbcf969cd9c28b0ab0f2a008efcf9a95 100644 GIT binary patch delta 1980 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 diff --git a/MailResources/Localizable/es.lproj/Localizable.strings b/MailResources/Localizable/es.lproj/Localizable.strings index 98a47474b9f37c2b3963c8d768c5ecdcacec2565..a802f9e02e1516c003b94068502c0f7dc10677ab 100644 GIT binary patch delta 2018 zcmX9;3pAG58vb5CpU*ETYbZsiX>>h8R+&c9Ow*V%YDzS%j&Tr1M{Y%Q{~k?|v`)eV_N)JN1HL{!zp1 zXOgEaB^7VG*KeK0JQtLDyb2^VmC)aO^)d za9S%N?K2IQtkuEfvlN7`Pe=DsE$r4vF!#wvp>MS!*ZIUib@LR=1Im%SPJ(Kk27_zM zF?)>`$)E3m*HR6R`xkNBUDWj#u=wUVgzo35C^?z+M}jpP=FJumT%VR9=ZTJm-030I z-EMB(JB1#SJUoLgw))vp8OaB2s5dB>_vIoi<<*aO<1B>n9zul>-p}~nBK~*bdKh2r zg?WOLpcZB-xJ3#+{2##)4CV+Wa-Z#j7e_Bt_*hvZxpfEEw!r4bGTS!_X2Bf0UBZYW zCkPLCYy=-nwEVy6w8SJ%2@4k@<&`$%!R{qm@UmScxGvr&FEk;k)kLBfB(D=mFTXaS z9yrB{_IV_hoL@KN!GvK>T3$G0tj2zs9j+xgQjDo2{N1n#fm>5# zFBNHJl_kZ%;fxg?S-s0^kr4E4C)&L7;N!kS@qTU8grgJklm_-!>_eeGMcH3w)WWkq zX6EJ-2uswdJf(=(zg5FIq8UkFox+o?sTh8d0q>yR%l5pl!B`3Csk@e+rmzu7~msIouUG5?>mz~D%D|&F}{d)MVC`Ci~MV?E-!nZnP z?rf2-P9r^sP>qn#avTrKhEZ5C)_s%+TNe$Iy0qL`&GwFR3jFJBDHe99@vNg0&K*tg zI(L`doV1i+J?Sh{eX)|TdSg2?YM)04Z7V~=u*lMW<@CQbq?bb`(skL%gv8b{kqn5f zx*kWe$uznsyP4A~`LHF4@+~W>l+#S`53ML$ZZo8Icy5w#=r5YqozqF;Fd2)e6Jy|-uSM07T(sqX4Rad_ zCAL|xJ)*&?#A0QReySRTM%SaNsS?&rS*ZQ690z}C#>fW+@cy6%|9slaG7oUlmS|q# zQOI=!o)B4Az~Ou&8!F8wQ0a4y ziWV$B8%e+AVL0hB?z}9*wG1_TmSs<{%z493F{Vq23%(}GT-T+7~tTvE9}7hIm9~VE+)*Mn~ULjxv;J)9uH&7 z-qjGyrs&wVccKU-vr1W^{Z{^{6iWyw(V?NZ5)CtdVa7)*2~G|YOlMZ(u;X*)>BOt{ z`O`}bT59CN$@GJ4Jehdv)gq$*oR6cQ-oO*@COBr)DAkd5J3A80TX9_7#|l?*$ooG! zuUPg^-Gkk8>d`Qz4<{F=Lgie|tP8nk{K5hFE~N}Y-omaYykqhCCtBc3ikrli2t$)PXntF9@L3$oJC-egq*+jDn3p< zZ!r2@B|^I|R+l1$HO%S@lpyK`u7TARX=^q zfqU7RT9Izr@6M>?ocd7%npig7JA5qn=r g$C5`#Zm=Us?zg8Cuy?bRlWq7lW_H9!)8C2z1CoMc0RR91 delta 4517 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 JaOmR6{{wI%cwPVi delta 4586 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 From 6882056796f3589c29ec90214178a7f94ce15034 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Tue, 20 Jun 2023 09:14:48 +0200 Subject: [PATCH 37/41] fix: Improve UI --- Mail/Components/UnknownRecipientView.swift | 2 -- Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift | 2 +- Mail/Views/New Message/ComposeMessageBodyView.swift | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Mail/Components/UnknownRecipientView.swift b/Mail/Components/UnknownRecipientView.swift index 613cf4e5d..b85944e08 100644 --- a/Mail/Components/UnknownRecipientView.swift +++ b/Mail/Components/UnknownRecipientView.swift @@ -23,8 +23,6 @@ import SwiftUI struct UnknownRecipientView: View { let size: CGFloat - static let imagePadding: CGFloat = 8 - private var iconSize: CGFloat { return size - 2 * UIConstants.unknownRecipientHorizontalPadding } diff --git a/Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift b/Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift index f34ffc4bc..78914808d 100644 --- a/Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift +++ b/Mail/Views/New Message/Attachments/AttachmentsHeaderView.swift @@ -37,8 +37,8 @@ struct AttachmentsHeaderView: View { } } .padding(.vertical, 1) + .padding(.horizontal, 16) } - .padding(.horizontal, 16) } } .customAlert(item: $attachmentsManager.globalError) { error in diff --git a/Mail/Views/New Message/ComposeMessageBodyView.swift b/Mail/Views/New Message/ComposeMessageBodyView.swift index 7ea4c8cd6..11f95de77 100644 --- a/Mail/Views/New Message/ComposeMessageBodyView.swift +++ b/Mail/Views/New Message/ComposeMessageBodyView.swift @@ -61,7 +61,7 @@ struct ComposeMessageBodyView: View { ) .ignoresSafeArea(.all, edges: .bottom) .frame(height: editorModel.height + 20) - .padding([.vertical], 8) + .padding(.vertical, 8) } .task { await prepareCompleteDraft() From d8df5d4ce301653244c8a361cd73484f7aa32443 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Tue, 20 Jun 2023 09:52:23 +0200 Subject: [PATCH 38/41] fix: Use newMessageCellAlignment --- .../Header Cells/ComposeMessageCellRecipients.swift | 12 +++++++++++- Mail/Views/New Message/RecipientField.swift | 10 ---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift index f85cc81e2..500c3b4d4 100644 --- a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift +++ b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift @@ -23,6 +23,16 @@ import MailResources import RealmSwift import SwiftUI +extension VerticalAlignment { + struct NewMessageCellAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[.firstTextBaseline] + } + } + + static let newMessageCellAlignment = VerticalAlignment(NewMessageCellAlignment.self) +} + struct ComposeMessageCellRecipients: View { @State private var currentText = "" @State private var autocompletion = [Recipient]() @@ -38,7 +48,7 @@ struct ComposeMessageCellRecipients: View { var body: some View { VStack(spacing: 0) { if autocompletionType == nil || autocompletionType == type { - HStack { + HStack(alignment: .newMessageCellAlignment) { Text(type.title) .textStyle(.bodySecondary) diff --git a/Mail/Views/New Message/RecipientField.swift b/Mail/Views/New Message/RecipientField.swift index 3e66628c6..ce0cde8fe 100644 --- a/Mail/Views/New Message/RecipientField.swift +++ b/Mail/Views/New Message/RecipientField.swift @@ -25,16 +25,6 @@ import RealmSwift import SwiftUI import WrappingHStack -extension VerticalAlignment { - private struct NewMessageCellAlignment: AlignmentID { - static func defaultValue(in context: ViewDimensions) -> CGFloat { - context[.firstTextBaseline] - } - } - - static let newMessageCellAlignment = VerticalAlignment(NewMessageCellAlignment.self) -} - struct RecipientField: View { @State private var keyboardHeight: CGFloat = 0 From 3dab97a88bdd1b4b6a0a7f261eb72b9a02f2622b Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Tue, 20 Jun 2023 11:25:37 +0200 Subject: [PATCH 39/41] feat: Add debounce --- .../New Message/AutocompletionView.swift | 18 ++++++++++------- .../ComposeMessageCellRecipients.swift | 20 ++++++++++++++----- Mail/Views/New Message/RecipientField.swift | 2 +- .../New Message/RecipientsTextField.swift | 1 - 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Mail/Views/New Message/AutocompletionView.swift b/Mail/Views/New Message/AutocompletionView.swift index 37abdf388..70551a712 100644 --- a/Mail/Views/New Message/AutocompletionView.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -16,6 +16,7 @@ along with this program. If not, see . */ +import Combine import MailCore import MailResources import RealmSwift @@ -24,8 +25,9 @@ import SwiftUI struct AutocompletionView: View { @State private var shouldAddUserProposal = false + @ObservedObject var cellRecipientsModel: CellRecipientsModel + @Binding var autocompletion: [Recipient] - @Binding var currentSearch: String @Binding var addedRecipients: RealmSwift.List let addRecipient: @MainActor (Recipient) -> Void @@ -40,7 +42,7 @@ struct AutocompletionView: View { AutocompletionCell( addRecipient: addRecipient, recipient: recipient, - highlight: currentSearch, + highlight: cellRecipientsModel.currentText, alreadyAppend: addedRecipients.contains { $0.isSameRecipient(as: recipient) }, unknownRecipient: isUserProposal ) @@ -52,9 +54,11 @@ struct AutocompletionView: View { } } .onAppear { - updateAutocompletion(currentSearch) + updateAutocompletion(cellRecipientsModel.currentText) + } + .onReceive(cellRecipientsModel.$currentText.debounce(for: .milliseconds(150), scheduler: DispatchQueue.main)) { currentValue in + updateAutocompletion("\(currentValue)") } - .onChange(of: currentSearch, perform: updateAutocompletion) } private func updateAutocompletion(_ search: String) { @@ -73,10 +77,10 @@ struct AutocompletionView: View { let realResults = autocompleteRecipients.filter { !addedRecipients.map(\.email).contains($0.email) } withAnimation { - shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == currentSearch) + shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == cellRecipientsModel.currentText) if shouldAddUserProposal { autocompleteRecipients - .append(Recipient(email: currentSearch, name: "")) + .append(Recipient(email: cellRecipientsModel.currentText, name: "")) } autocompletion = autocompleteRecipients @@ -87,8 +91,8 @@ struct AutocompletionView: View { struct AutocompletionView_Previews: PreviewProvider { static var previews: some View { AutocompletionView( + cellRecipientsModel: CellRecipientsModel(), autocompletion: .constant([]), - currentSearch: .constant(""), addedRecipients: .constant([PreviewHelper.sampleRecipient1].toRealmList()) ) { _ in /* Preview */ } } diff --git a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift index 500c3b4d4..515b1af8f 100644 --- a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift +++ b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift @@ -33,8 +33,13 @@ extension VerticalAlignment { static let newMessageCellAlignment = VerticalAlignment(NewMessageCellAlignment.self) } +class CellRecipientsModel: ObservableObject { + @Published var currentText = "" +} + struct ComposeMessageCellRecipients: View { - @State private var currentText = "" + @StateObject private var cellRecipientsModel = CellRecipientsModel() + @State private var autocompletion = [Recipient]() @Binding var recipients: RealmSwift.List @@ -52,7 +57,12 @@ struct ComposeMessageCellRecipients: View { Text(type.title) .textStyle(.bodySecondary) - RecipientField(currentText: $currentText, recipients: $recipients, focusedField: _focusedField, type: type) { + RecipientField( + currentText: $cellRecipientsModel.currentText, + recipients: $recipients, + focusedField: _focusedField, + type: type + ) { if let bestMatch = autocompletion.first { addNewRecipient(bestMatch) } @@ -70,8 +80,8 @@ struct ComposeMessageCellRecipients: View { if autocompletionType == type { AutocompletionView( + cellRecipientsModel: cellRecipientsModel, autocompletion: $autocompletion, - currentSearch: $currentText, addedRecipients: $recipients, addRecipient: addNewRecipient ) @@ -81,7 +91,7 @@ struct ComposeMessageCellRecipients: View { .onTapGesture { focusedField = type } - .onChange(of: currentText) { newValue in + .onChange(of: cellRecipientsModel.currentText) { newValue in withAnimation { if newValue.isEmpty { autocompletionType = nil @@ -109,7 +119,7 @@ struct ComposeMessageCellRecipients: View { withAnimation { $recipients.append(recipient) } - currentText = "" + cellRecipientsModel.currentText = "" } } diff --git a/Mail/Views/New Message/RecipientField.swift b/Mail/Views/New Message/RecipientField.swift index ce0cde8fe..d230e619d 100644 --- a/Mail/Views/New Message/RecipientField.swift +++ b/Mail/Views/New Message/RecipientField.swift @@ -82,7 +82,7 @@ struct RecipientField: View { } private func switchFocus() { - guard case .chip(let hash, let recipient) = focusedField else { return } + guard case let .chip(hash, recipient) = focusedField else { return } if recipient == recipients.last { focusedField = type diff --git a/Mail/Views/New Message/RecipientsTextField.swift b/Mail/Views/New Message/RecipientsTextField.swift index 85b20c0ba..d5ba7d662 100644 --- a/Mail/Views/New Message/RecipientsTextField.swift +++ b/Mail/Views/New Message/RecipientsTextField.swift @@ -95,4 +95,3 @@ class UIRecipientsTextField: UITextField { super.deleteBackward() } } - From 5292452caea6e83cd801193e9e0203e4b62aaf2e Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Tue, 20 Jun 2023 12:30:55 +0200 Subject: [PATCH 40/41] feat(ComposeMessageView): Rename extension file --- ...eMessageView+Extension.swift => ComposeMessageView+Init.swift} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Mail/Views/New Message/{ComposeMessageView+Extension.swift => ComposeMessageView+Init.swift} (100%) diff --git a/Mail/Views/New Message/ComposeMessageView+Extension.swift b/Mail/Views/New Message/ComposeMessageView+Init.swift similarity index 100% rename from Mail/Views/New Message/ComposeMessageView+Extension.swift rename to Mail/Views/New Message/ComposeMessageView+Init.swift From 51b615680d6b73208741499302487ae60362557e Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Tue, 20 Jun 2023 12:40:49 +0200 Subject: [PATCH 41/41] refactor: Rename observable object for debounce --- Mail/Views/New Message/AutocompletionView.swift | 14 +++++++------- .../ComposeMessageCellRecipients.swift | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Mail/Views/New Message/AutocompletionView.swift b/Mail/Views/New Message/AutocompletionView.swift index 70551a712..aed0bd1fc 100644 --- a/Mail/Views/New Message/AutocompletionView.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -25,7 +25,7 @@ import SwiftUI struct AutocompletionView: View { @State private var shouldAddUserProposal = false - @ObservedObject var cellRecipientsModel: CellRecipientsModel + @ObservedObject var textDebounce: TextDebounce @Binding var autocompletion: [Recipient] @Binding var addedRecipients: RealmSwift.List @@ -42,7 +42,7 @@ struct AutocompletionView: View { AutocompletionCell( addRecipient: addRecipient, recipient: recipient, - highlight: cellRecipientsModel.currentText, + highlight: textDebounce.text, alreadyAppend: addedRecipients.contains { $0.isSameRecipient(as: recipient) }, unknownRecipient: isUserProposal ) @@ -54,9 +54,9 @@ struct AutocompletionView: View { } } .onAppear { - updateAutocompletion(cellRecipientsModel.currentText) + updateAutocompletion(textDebounce.text) } - .onReceive(cellRecipientsModel.$currentText.debounce(for: .milliseconds(150), scheduler: DispatchQueue.main)) { currentValue in + .onReceive(textDebounce.$text.debounce(for: .milliseconds(150), scheduler: DispatchQueue.main)) { currentValue in updateAutocompletion("\(currentValue)") } } @@ -77,10 +77,10 @@ struct AutocompletionView: View { let realResults = autocompleteRecipients.filter { !addedRecipients.map(\.email).contains($0.email) } withAnimation { - shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == cellRecipientsModel.currentText) + shouldAddUserProposal = !(realResults.count == 1 && realResults.first?.email == textDebounce.text) if shouldAddUserProposal { autocompleteRecipients - .append(Recipient(email: cellRecipientsModel.currentText, name: "")) + .append(Recipient(email: textDebounce.text, name: "")) } autocompletion = autocompleteRecipients @@ -91,7 +91,7 @@ struct AutocompletionView: View { struct AutocompletionView_Previews: PreviewProvider { static var previews: some View { AutocompletionView( - cellRecipientsModel: CellRecipientsModel(), + textDebounce: TextDebounce(), autocompletion: .constant([]), addedRecipients: .constant([PreviewHelper.sampleRecipient1].toRealmList()) ) { _ in /* Preview */ } diff --git a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift index 515b1af8f..de0ecae23 100644 --- a/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift +++ b/Mail/Views/New Message/Header Cells/ComposeMessageCellRecipients.swift @@ -33,12 +33,12 @@ extension VerticalAlignment { static let newMessageCellAlignment = VerticalAlignment(NewMessageCellAlignment.self) } -class CellRecipientsModel: ObservableObject { - @Published var currentText = "" +class TextDebounce: ObservableObject { + @Published var text = "" } struct ComposeMessageCellRecipients: View { - @StateObject private var cellRecipientsModel = CellRecipientsModel() + @StateObject private var textDebounce = TextDebounce() @State private var autocompletion = [Recipient]() @@ -58,7 +58,7 @@ struct ComposeMessageCellRecipients: View { .textStyle(.bodySecondary) RecipientField( - currentText: $cellRecipientsModel.currentText, + currentText: $textDebounce.text, recipients: $recipients, focusedField: _focusedField, type: type @@ -80,7 +80,7 @@ struct ComposeMessageCellRecipients: View { if autocompletionType == type { AutocompletionView( - cellRecipientsModel: cellRecipientsModel, + textDebounce: textDebounce, autocompletion: $autocompletion, addedRecipients: $recipients, addRecipient: addNewRecipient @@ -91,7 +91,7 @@ struct ComposeMessageCellRecipients: View { .onTapGesture { focusedField = type } - .onChange(of: cellRecipientsModel.currentText) { newValue in + .onChange(of: textDebounce.text) { newValue in withAnimation { if newValue.isEmpty { autocompletionType = nil @@ -119,7 +119,7 @@ struct ComposeMessageCellRecipients: View { withAnimation { $recipients.append(recipient) } - cellRecipientsModel.currentText = "" + textDebounce.text = "" } }