From bfb502feb2345a8f1efc15d93316bbd1abb7273b Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Mon, 3 Apr 2023 15:47:45 +0200 Subject: [PATCH] refactor: Update autocompletion view --- Mail/Components/RecipientField.swift | 19 +++-- Mail/Views/New Message/AddRecipientCell.swift | 57 ++++++++++++++ .../New Message/AutocompletionView.swift | 14 +++- .../New Message/ComposeMessageView.swift | 78 ++++++++++--------- Mail/Views/New Message/NewMessageCell.swift | 1 + .../user-bold.imageset/Contents.json | 16 ++++ .../user-bold.imageset/user-bold.svg | 4 + 7 files changed, 146 insertions(+), 43 deletions(-) create mode 100644 Mail/Views/New Message/AddRecipientCell.swift create mode 100644 MailResources/Assets.xcassets/user-bold.imageset/Contents.json create mode 100644 MailResources/Assets.xcassets/user-bold.imageset/user-bold.svg diff --git a/Mail/Components/RecipientField.swift b/Mail/Components/RecipientField.swift index a5b7128908..1815874d86 100644 --- a/Mail/Components/RecipientField.swift +++ b/Mail/Components/RecipientField.swift @@ -47,6 +47,7 @@ struct RecipientChip: View { struct RecipientField: View { @Binding var recipients: RealmSwift.List @Binding var autocompletion: [Recipient] + @Binding var unknownRecipientAutocompletion: String @Binding var addRecipientHandler: ((Recipient) -> Void)? @FocusState var focusedField: ComposeViewFieldType? let type: ComposeViewFieldType @@ -95,16 +96,21 @@ struct RecipientField: View { } private func updateAutocompletion() { + let trimmedCurrentText = currentText.trimmingCharacters(in: .whitespacesAndNewlines) + let contactManager = AccountManager.instance.currentContactManager - let autocompleteContacts = contactManager?.contacts(matching: currentText) ?? [] + let autocompleteContacts = contactManager?.contacts(matching: trimmedCurrentText) ?? [] var autocompleteRecipients = autocompleteContacts.map { Recipient(email: $0.email, name: $0.name) } - // Append typed email - if !currentText.isEmpty && !autocompletion - .contains(where: { $0.email.caseInsensitiveCompare(currentText) == .orderedSame }) { - autocompleteRecipients.append(Recipient(email: currentText, 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 = "" + } } } @@ -135,6 +141,7 @@ struct RecipientField_Previews: PreviewProvider { 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 new file mode 100644 index 0000000000..ba8c6c650a --- /dev/null +++ b/Mail/Views/New Message/AddRecipientCell.swift @@ -0,0 +1,57 @@ +/* + 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 index 98d7d8f1c1..4fb834bee3 100644 --- a/Mail/Views/New Message/AutocompletionView.swift +++ b/Mail/Views/New Message/AutocompletionView.swift @@ -22,6 +22,8 @@ import SwiftUI struct AutocompletionView: View { @Binding var autocompletion: [Recipient] + @Binding var unknownRecipientAutocompletion: String + let onSelect: (Recipient) -> Void var body: some View { @@ -37,10 +39,19 @@ struct AutocompletionView: View { IKDivider() } + } + + if !unknownRecipientAutocompletion.isEmpty { + Button { + onSelect(Recipient(email: unknownRecipientAutocompletion, name: "")) + } label: { + AddRecipientCell(recipientEmail: unknownRecipientAutocompletion) + } .padding(.horizontal, 8) } } .padding(.top, 8) + .padding(.horizontal, 8) } } @@ -48,6 +59,7 @@ struct AutocompletionView_Previews: PreviewProvider { static var previews: some View { AutocompletionView(autocompletion: .constant([ PreviewHelper.sampleRecipient1, PreviewHelper.sampleRecipient2, PreviewHelper.sampleRecipient3 - ])) { _ in /* Preview */ } + ]), + unknownRecipientAutocompletion: .constant("")) { _ in /* Preview */ } } } diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift index 03143d072e..49d4b46ac8 100644 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -70,6 +70,8 @@ struct ComposeMessageView: View { @StateObject private var attachmentsManager: AttachmentsManager @State private var isShowingCancelAttachmentsError = false + @State private var unknownRecipientAutocompletion = "" + @State var scrollView: UIScrollView? @StateObject private var alert = NewMessageAlert() @@ -81,7 +83,7 @@ struct ComposeMessageView: View { } private var shouldDisplayAutocompletion: Bool { - return !autocompletion.isEmpty && focusedField != nil + return (!autocompletion.isEmpty || !unknownRecipientAutocompletion.isEmpty) && focusedField != nil } private init(mailboxManager: MailboxManager, draft: Draft) { @@ -103,39 +105,6 @@ struct ComposeMessageView: View { _attachmentsManager = StateObject(wrappedValue: AttachmentsManager(draft: draft, mailboxManager: mailboxManager)) } - static func newMessage(mailboxManager: MailboxManager) -> ComposeMessageView { - return ComposeMessageView(mailboxManager: mailboxManager, draft: Draft(localUUID: UUID().uuidString)) - } - - static func replyOrForwardMessage(messageReply: MessageReply, mailboxManager: MailboxManager) -> ComposeMessageView { - let message = messageReply.message - // If message doesn't exist anymore try to show the frozen one - let freshMessage = message.thaw() ?? message - return ComposeMessageView( - mailboxManager: mailboxManager, - draft: .replying(to: freshMessage, mode: messageReply.replyMode, localDraftUUID: messageReply.localDraftUUID) - ) - } - - 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"), - to: [Recipient(email: urlComponents.path, name: "")], - cc: Recipient.createListUsing(from: urlComponents, name: "cc"), - bcc: Recipient.createListUsing(from: urlComponents, name: "bcc")) - return ComposeMessageView(mailboxManager: mailboxManager, draft: draft) - } - var body: some View { NavigationView { ScrollView(.vertical, showsIndicators: true) { @@ -157,7 +126,8 @@ struct ComposeMessageView: View { // Show the rest of the view, or the autocompletion list if shouldDisplayAutocompletion { - AutocompletionView(autocompletion: $autocompletion) { recipient in + AutocompletionView(autocompletion: $autocompletion, + unknownRecipientAutocompletion: $unknownRecipientAutocompletion) { recipient in matomo.track(eventWithCategory: .newMessage, name: "addNewRecipient") addRecipientHandler?(recipient) } @@ -281,6 +251,7 @@ struct ComposeMessageView: View { showCc: type == .to ? $showCc : nil) { RecipientField(recipients: binding(for: type), autocompletion: $autocompletion, + unknownRecipientAutocompletion: $unknownRecipientAutocompletion, addRecipientHandler: $addRecipientHandler, focusedField: _focusedField, type: type) @@ -333,7 +304,42 @@ struct ComposeMessageView: View { } } -struct NewMessageView_Previews: PreviewProvider { +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 { + let message = messageReply.message + // If message doesn't exist anymore try to show the frozen one + let freshMessage = message.thaw() ?? message + return ComposeMessageView( + mailboxManager: mailboxManager, + draft: .replying(to: freshMessage, mode: messageReply.replyMode, localDraftUUID: messageReply.localDraftUUID) + ) + } + + 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"), + to: [Recipient(email: urlComponents.path, name: "")], + cc: Recipient.createListUsing(from: urlComponents, name: "cc"), + bcc: Recipient.createListUsing(from: urlComponents, name: "bcc")) + return ComposeMessageView(mailboxManager: mailboxManager, draft: draft) + } +} + +struct ComposeMessageView_Previews: PreviewProvider { static var previews: some View { ComposeMessageView.newMessage(mailboxManager: PreviewHelper.sampleMailboxManager) } diff --git a/Mail/Views/New Message/NewMessageCell.swift b/Mail/Views/New Message/NewMessageCell.swift index 4b0564ca7d..c64b07b01e 100644 --- a/Mail/Views/New Message/NewMessageCell.swift +++ b/Mail/Views/New Message/NewMessageCell.swift @@ -83,6 +83,7 @@ struct RecipientCellView_Previews: PreviewProvider { showCc: .constant(false)) { RecipientField(recipients: .constant([PreviewHelper.sampleRecipient1].toRealmList()), autocompletion: .constant([]), + unknownRecipientAutocompletion: .constant(""), addRecipientHandler: .constant { _ in /* Preview */ }, focusedField: .init(), type: .to) diff --git a/MailResources/Assets.xcassets/user-bold.imageset/Contents.json b/MailResources/Assets.xcassets/user-bold.imageset/Contents.json new file mode 100644 index 0000000000..23e92c1f55 --- /dev/null +++ b/MailResources/Assets.xcassets/user-bold.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "user-bold.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/MailResources/Assets.xcassets/user-bold.imageset/user-bold.svg b/MailResources/Assets.xcassets/user-bold.imageset/user-bold.svg new file mode 100644 index 0000000000..6da2d82c0d --- /dev/null +++ b/MailResources/Assets.xcassets/user-bold.imageset/user-bold.svg @@ -0,0 +1,4 @@ + + + +