From da0083d95daf49b052f5d871f1420a2bcd24ca49 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 17 Feb 2022 22:35:44 +0200 Subject: [PATCH 01/22] #1337 add RecipientBase protocol --- FlowCrypt.xcodeproj/project.pbxproj | 16 ++++++++++ .../Compose/ComposeViewController.swift | 23 ++++++++------- .../CloudContactsProvider.swift | 22 +++++++------- .../Model/CloudContact.swift | 29 +++++++++++++++++++ .../Contacts Service/ContactsService.swift | 6 ++-- .../LocalContactsProvider.swift | 11 +++---- FlowCrypt/Models/Common/Recipient.swift | 2 +- FlowCrypt/Models/Common/RecipientBase.swift | 21 ++++++++++++++ .../Realm Models/RecipientRealmObject.swift | 2 +- Podfile.lock | 4 +-- 10 files changed, 101 insertions(+), 35 deletions(-) create mode 100644 FlowCrypt/Functionality/Mail Provider/Contacts Provider/Model/CloudContact.swift create mode 100644 FlowCrypt/Models/Common/RecipientBase.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 2120c6d4d..2bc29ae97 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -61,6 +61,8 @@ 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA9701B2D5052225A0414 /* SignInViewController.swift */; }; 50531BE42629B9A80039BAE9 /* AttachmentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50531BE32629B9A80039BAE9 /* AttachmentNode.swift */; }; 5109A77C272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5109A77B272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift */; }; + 510BB63527BE92CC00B1011F /* RecipientBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63427BE92CC00B1011F /* RecipientBase.swift */; }; + 510BB63827BE9B1300B1011F /* CloudContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63727BE9B1300B1011F /* CloudContact.swift */; }; 511D07E12769FBBA0050417B /* MessagePasswordCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D07E02769FBBA0050417B /* MessagePasswordCellNode.swift */; }; 511D07E3276A2DF80050417B /* ButtonWithPaddingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */; }; 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */ = {isa = PBXBuildFile; productRef = 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */; }; @@ -501,6 +503,8 @@ 4F928D493732294B4E521900 /* Pods-FlowCryptUIApplication.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUIApplication.release.xcconfig"; path = "Target Support Files/Pods-FlowCryptUIApplication/Pods-FlowCryptUIApplication.release.xcconfig"; sourceTree = ""; }; 50531BE32629B9A80039BAE9 /* AttachmentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentNode.swift; sourceTree = ""; }; 5109A77B272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftAlignedCollectionViewFlowLayout.swift; sourceTree = ""; }; + 510BB63427BE92CC00B1011F /* RecipientBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientBase.swift; sourceTree = ""; }; + 510BB63727BE9B1300B1011F /* CloudContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudContact.swift; sourceTree = ""; }; 511D07E02769FBBA0050417B /* MessagePasswordCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePasswordCellNode.swift; sourceTree = ""; }; 511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonWithPaddingNode.swift; sourceTree = ""; }; 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailViewController.swift; sourceTree = ""; }; @@ -988,6 +992,7 @@ 2C141B2E274578C20038A3F8 /* Keypair.swift */, 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */, 2C141B2B274572D50038A3F8 /* Recipient.swift */, + 510BB63427BE92CC00B1011F /* RecipientBase.swift */, 2CC50FAE27440B2C0051629A /* Session.swift */, 9F0C3C132316E69300299985 /* User.swift */, ); @@ -1049,6 +1054,14 @@ path = SignIn; sourceTree = ""; }; + 510BB63627BE9AEB00B1011F /* Model */ = { + isa = PBXGroup; + children = ( + 510BB63727BE9B1300B1011F /* CloudContact.swift */, + ); + path = Model; + sourceTree = ""; + }; 5161D87D27A488BD00130518 /* Contacts Service */ = { isa = PBXGroup; children = ( @@ -1639,6 +1652,7 @@ 9FFC7E60260C948200282FCE /* Contacts Provider */ = { isa = PBXGroup; children = ( + 510BB63627BE9AEB00B1011F /* Model */, 9FFC7E59260C946000282FCE /* CloudContactsProvider.swift */, D29AFFF82409767F00C1387D /* GoogleContactsResponse.swift */, ); @@ -2644,6 +2658,7 @@ 9F3EF32F23B172D300FA0CEF /* SearchViewController.swift in Sources */, F191F621272511790053833E /* BlurViewController.swift in Sources */, D2F6D147243506DA00DB4065 /* MailSettingsCredentials.swift in Sources */, + 510BB63827BE9B1300B1011F /* CloudContact.swift in Sources */, 9F9361A52573CE260009912F /* MessageProvider.swift in Sources */, 32DCA2BB910FA1B19BA8B328 /* Imap.swift in Sources */, D274724424FD1932006BA6EF /* FolderRealmObject.swift in Sources */, @@ -2713,6 +2728,7 @@ 9F31AB8E23298BCF00CF87EA /* Imap+folders.swift in Sources */, D2891AC224C59EFA008918E3 /* KeyService.swift in Sources */, D269E02724103A20000495C3 /* ComposeViewControllerInput.swift in Sources */, + 510BB63527BE92CC00B1011F /* RecipientBase.swift in Sources */, 9FAFD75D2714A06400321FA4 /* InboxProviders.swift in Sources */, 2C141B2C274572D50038A3F8 /* Recipient.swift in Sources */, 9F0C3C142316E69300299985 /* User.swift in Sources */, diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 07b3500da..49b3f1c9e 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -34,7 +34,7 @@ final class ComposeViewController: TableNodeViewController { } enum State { - case main, searchEmails([String]) + case main, searchEmails([RecipientBase]) } private enum Section: Int, CaseIterable { @@ -600,10 +600,10 @@ extension ComposeViewController: ASTableDelegate, ASTableDataSource { return ASCellNode() } return self.attachmentNode(for: indexPath.row) - case let (.searchEmails(emails), 1): + case let (.searchEmails(recipients), 1): guard indexPath.row > 0 else { return DividerCellNode() } - guard emails.isNotEmpty else { return self.noSearchResultsNode() } - return InfoCellNode(input: self.decorator.styledRecipientInfo(with: emails[indexPath.row-1])) + guard recipients.isNotEmpty else { return self.noSearchResultsNode() } + return InfoCellNode(input: self.decorator.styledRecipientInfo(with: recipients[indexPath.row-1].email)) case (.searchEmails, 2): return indexPath.row == 0 ? DividerCellNode() : self.enableGoogleContactsNode() default: @@ -613,10 +613,10 @@ extension ComposeViewController: ASTableDelegate, ASTableDataSource { } func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { - if case let .searchEmails(emails) = state { + if case let .searchEmails(recipients) = state { switch indexPath.section { case 1: - let selectedEmail = emails[safe: indexPath.row-1] + let selectedEmail = recipients[safe: indexPath.row-1]?.email handleEndEditingAction(with: selectedEmail) case 2: askForContactsPermission() @@ -945,10 +945,11 @@ extension ComposeViewController { private func searchEmail(with query: String) { Task { do { - let localEmails = try contactsService.searchLocalContacts(query: query) - let cloudEmails = try? await service.searchContacts(query: query) - let emails = Set([localEmails, cloudEmails].compactMap { $0 }.flatMap { $0 }) - updateState(with: .searchEmails(Array(emails))) + let localRecipients = try contactsService.searchLocalContacts(query: query) + let cloudRecipients = try await service.searchContacts(query: query) + let recipients = localRecipients + cloudRecipients + // TODO: Add check for unique + updateState(with: .searchEmails(recipients)) } catch { showAlert(message: error.localizedDescription) } @@ -1427,7 +1428,7 @@ private actor ServiceActor { ) } - func searchContacts(query: String) async throws -> [String] { + func searchContacts(query: String) async throws -> [RecipientBase] { return try await cloudContactProvider.searchContacts(query: query) } diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift index f40321669..4b7444eed 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift @@ -11,7 +11,7 @@ import GoogleAPIClientForREST_PeopleService protocol CloudContactsProvider { var isContactsScopeEnabled: Bool { get } - func searchContacts(query: String) async throws -> [String] + func searchContacts(query: String) async throws -> [RecipientBase] } enum CloudContactsProviderError: Error { @@ -81,24 +81,26 @@ final class UserContactsProvider { } extension UserContactsProvider: CloudContactsProvider { - func searchContacts(query: String) async -> [String] { + func searchContacts(query: String) async -> [RecipientBase] { guard isContactsScopeEnabled else { return [] } let contacts = await searchUserContacts(query: query, type: .contacts) let otherContacts = await searchUserContacts(query: query, type: .other) - let emails = Set(contacts + otherContacts) - return Array(emails).sorted(by: >) + return contacts + otherContacts + // TODO + // let emails = Set(contacts + otherContacts) + // return Array(emails).sorted(by: >) } } extension UserContactsProvider { - private func searchUserContacts(query: String, type: QueryType) async -> [String] { + private func searchUserContacts(query: String, type: QueryType) async -> [RecipientBase] { let query = type.query(searchString: query) guard let emails = try? await perform(query: query) else { return [] } return emails } - private func perform(query: GTLRPeopleServiceQuery) async throws -> [String] { + private func perform(query: GTLRPeopleServiceQuery) async throws -> [RecipientBase] { try await withCheckedThrowingContinuation { continuation in self.peopleService.executeQuery(query) { _, data, error in if let error = error { @@ -113,12 +115,8 @@ extension UserContactsProvider { return continuation.resume(throwing: CloudContactsProviderError.failedToParseData(data)) } - let emails = contacts - .compactMap { $0.person?.emailAddresses } - .flatMap { $0 } - .compactMap { $0.value } - - return continuation.resume(returning: emails) + let recipients = contacts.compactMap(\.person).compactMap(CloudContact.init) + return continuation.resume(returning: recipients) } } } diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/Model/CloudContact.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/Model/CloudContact.swift new file mode 100644 index 000000000..ab2709626 --- /dev/null +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/Model/CloudContact.swift @@ -0,0 +1,29 @@ +// +// CloudContact.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 17/02/22 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation +import GoogleAPIClientForREST_PeopleService + +struct CloudContact: RecipientBase, Hashable { + let email: String + let name: String? +} + +extension CloudContact { + init?(person: GTLRPeopleService_Person) { + guard let email = person.emailAddresses?.first?.value else { return nil } + + self.email = email + + if let name = person.names?.first { + self.name = [name.givenName, name.familyName].compactMap { $0 }.joined(separator: " ") + } else { + self.name = nil + } + } +} diff --git a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift index ac090c4e3..772273269 100644 --- a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift @@ -18,7 +18,7 @@ protocol ContactsServiceType: PublicKeyProvider, ContactsProviderType { protocol ContactsProviderType { func findLocalContact(with email: String) async throws -> RecipientWithSortedPubKeys? - func searchLocalContacts(query: String) throws -> [String] + func searchLocalContacts(query: String) throws -> [RecipientBase] func fetchContact(with email: String) async throws -> RecipientWithSortedPubKeys } @@ -47,8 +47,8 @@ extension ContactsService: ContactsProviderType { return try await localContactsProvider.searchRecipient(with: email) } - func searchLocalContacts(query: String) throws -> [String] { - try localContactsProvider.searchEmails(query: query) + func searchLocalContacts(query: String) throws -> [RecipientBase] { + try localContactsProvider.searchRecipients(query: query) } func fetchContact(with email: String) async throws -> RecipientWithSortedPubKeys { diff --git a/FlowCrypt/Functionality/Services/Local Contacts Service/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Local Contacts Service/LocalContactsProvider.swift index 041a34a47..caa91e17f 100644 --- a/FlowCrypt/Functionality/Services/Local Contacts Service/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Local Contacts Service/LocalContactsProvider.swift @@ -12,7 +12,7 @@ import FlowCryptCommon protocol LocalContactsProviderType: PublicKeyProvider { func searchRecipient(with email: String) async throws -> RecipientWithSortedPubKeys? - func searchEmails(query: String) throws -> [String] + func searchRecipients(query: String) throws -> [RecipientBase] func save(recipient: RecipientWithSortedPubKeys) throws func remove(recipient: RecipientWithSortedPubKeys) throws func updateKeys(for recipient: RecipientWithSortedPubKeys) throws @@ -91,10 +91,11 @@ extension LocalContactsProvider: LocalContactsProviderType { return try await parseRecipient(from: recipient) } - func searchEmails(query: String) throws -> [String] { - try storage.objects(RecipientRealmObject.self) - .filter("email contains[c] %@", query) - .map(\.email) + func searchRecipients(query: String) throws -> [RecipientBase] { + let recipients = try storage + .objects(RecipientRealmObject.self) + .filter("email contains[c] %@", query) + return Array(recipients) } func getAllRecipients() async throws -> [RecipientWithSortedPubKeys] { diff --git a/FlowCrypt/Models/Common/Recipient.swift b/FlowCrypt/Models/Common/Recipient.swift index d4ebf5f34..d927fabb8 100644 --- a/FlowCrypt/Models/Common/Recipient.swift +++ b/FlowCrypt/Models/Common/Recipient.swift @@ -8,7 +8,7 @@ import Foundation -struct Recipient { +struct Recipient: RecipientBase { var email: String var name: String? var lastUsed: Date? diff --git a/FlowCrypt/Models/Common/RecipientBase.swift b/FlowCrypt/Models/Common/RecipientBase.swift new file mode 100644 index 000000000..cb26d1adc --- /dev/null +++ b/FlowCrypt/Models/Common/RecipientBase.swift @@ -0,0 +1,21 @@ +// +// RecipientBase.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 17/02/22 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +protocol RecipientBase { + var email: String { get } + var name: String? { get } +} + +extension RecipientBase { + var formatted: String { + guard let name = name else { return email } + return "\(name) <\(email)>" + } +} diff --git a/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift b/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift index 99bca1721..7bb7a5bb2 100644 --- a/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift @@ -9,7 +9,7 @@ import Foundation import RealmSwift -final class RecipientRealmObject: Object { +final class RecipientRealmObject: Object, RecipientBase { @Persisted(primaryKey: true) var email: String @Persisted var name: String? @Persisted var lastUsed: Date? diff --git a/Podfile.lock b/Podfile.lock index c2c55c34e..5cde9aa36 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,7 +15,7 @@ PODS: - PINRemoteImage/PINCache (3.0.3): - PINCache (~> 3.0.3) - PINRemoteImage/Core - - SwiftFormat/CLI (0.49.3) + - SwiftFormat/CLI (0.49.4) - SwiftLint (0.46.2) - SwiftyRSA (1.7.0): - SwiftyRSA/ObjC (= 1.7.0) @@ -64,7 +64,7 @@ SPEC CHECKSUMS: PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01 - SwiftFormat: a3b79e8b5f8ecdec7a716b998aee230d08512894 + SwiftFormat: 8acc16efcecb563206cbe90b4cb047cfdf9aafdb SwiftLint: 6bc52a21f0fd44cab9aa2dc8e534fb9f5e3ec507 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3 From 6a37325b9db2407f97eacda7e488babf25b3efae Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Fri, 18 Feb 2022 23:21:40 +0200 Subject: [PATCH 02/22] #1337 add names to recipients --- FlowCrypt.xcodeproj/project.pbxproj | 4 ++ .../Compose/ComposeViewController.swift | 23 +++++++--- .../Compose/ComposeViewControllerInput.swift | 6 +-- .../Compose/ComposeViewDecorator.swift | 25 +++++++++-- .../Controllers/Inbox/InboxRenderable.swift | 5 +-- .../Threads/ThreadDetailsDecorator.swift | 4 +- .../Threads/ThreadDetailsViewController.swift | 9 ++-- .../CloudContactsProvider.swift | 2 +- .../Message Provider/MessageService.swift | 2 +- .../Imap+MessagesList.swift | 7 ++- .../MessagesList Provider/Model/Message.swift | 44 +++---------------- .../Model/MessageRecipient.swift | 41 +++++++++++++++++ .../Threads/MessagesThreadProvider.swift | 21 ++++----- .../ComposeMessageRecipient.swift | 1 + FlowCryptUI/Cell Nodes/LabelCellNode.swift | 7 ++- 15 files changed, 124 insertions(+), 77 deletions(-) create mode 100644 FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 2bc29ae97..fabc22b04 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ 5109A77C272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5109A77B272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift */; }; 510BB63527BE92CC00B1011F /* RecipientBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63427BE92CC00B1011F /* RecipientBase.swift */; }; 510BB63827BE9B1300B1011F /* CloudContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63727BE9B1300B1011F /* CloudContact.swift */; }; + 510BB63A27BFD6C900B1011F /* MessageRecipient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63927BFD6C800B1011F /* MessageRecipient.swift */; }; 511D07E12769FBBA0050417B /* MessagePasswordCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D07E02769FBBA0050417B /* MessagePasswordCellNode.swift */; }; 511D07E3276A2DF80050417B /* ButtonWithPaddingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */; }; 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */ = {isa = PBXBuildFile; productRef = 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */; }; @@ -505,6 +506,7 @@ 5109A77B272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftAlignedCollectionViewFlowLayout.swift; sourceTree = ""; }; 510BB63427BE92CC00B1011F /* RecipientBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientBase.swift; sourceTree = ""; }; 510BB63727BE9B1300B1011F /* CloudContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudContact.swift; sourceTree = ""; }; + 510BB63927BFD6C800B1011F /* MessageRecipient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRecipient.swift; sourceTree = ""; }; 511D07E02769FBBA0050417B /* MessagePasswordCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePasswordCellNode.swift; sourceTree = ""; }; 511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonWithPaddingNode.swift; sourceTree = ""; }; 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailViewController.swift; sourceTree = ""; }; @@ -1625,6 +1627,7 @@ 9FE1B39F2565B0CD00D6D086 /* Message.swift */, 9F5C2A76257D705100DE9B4B /* MessageLabel.swift */, 51938DC0274CC291007AD57B /* MessageQuoteType.swift */, + 510BB63927BFD6C800B1011F /* MessageRecipient.swift */, ); path = Model; sourceTree = ""; @@ -2782,6 +2785,7 @@ D2F6D1332433753100DB4065 /* IMAPConnectionParameters.swift in Sources */, 9F003DB625EA92BC00EB38C0 /* LogOutHandler.swift in Sources */, D20D3C6E2520AB3900D4AA9A /* BackupViewDecorator.swift in Sources */, + 510BB63A27BFD6C900B1011F /* MessageRecipient.swift in Sources */, 9F268891237DC55600428A94 /* SetupManuallyImportKeyViewController.swift in Sources */, D2E26F7024F266F300612AF1 /* ContactDetailViewController.swift in Sources */, 5A39F43F239EE7D2001F4607 /* SegmentedViewController.swift in Sources */, diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 49b3f1c9e..576528250 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -202,7 +202,7 @@ final class ComposeViewController: TableNodeViewController { func update(with message: Message) { self.contextToSend.subject = message.subject self.contextToSend.message = message.raw - self.contextToSend.recipients = [ComposeMessageRecipient(email: "tom@flowcrypt.com", state: decorator.recipientIdleState)] + self.contextToSend.recipients = [ComposeMessageRecipient(email: "tom@flowcrypt.com", name: "Tom", state: decorator.recipientIdleState)] } private func observeComposeUpdates() { @@ -322,8 +322,12 @@ extension ComposeViewController { private func setupQuote() { guard input.isQuote else { return } - input.quoteRecipients.forEach { email in - let recipient = ComposeMessageRecipient(email: email, state: decorator.recipientIdleState) + input.quoteRecipients.forEach { recipient in + let recipient = ComposeMessageRecipient( + email: recipient.email, + name: recipient.name, + state: decorator.recipientIdleState + ) contextToSend.recipients.append(recipient) evaluate(recipient: recipient) } @@ -603,7 +607,15 @@ extension ComposeViewController: ASTableDelegate, ASTableDataSource { case let (.searchEmails(recipients), 1): guard indexPath.row > 0 else { return DividerCellNode() } guard recipients.isNotEmpty else { return self.noSearchResultsNode() } - return InfoCellNode(input: self.decorator.styledRecipientInfo(with: recipients[indexPath.row-1].email)) + + let recipient = recipients[indexPath.row-1] + + if let name = recipient.name { + return LabelCellNode(input: self.decorator.styledRecipientInfo(with: recipient.email, name: name)) + } else { + return InfoCellNode(input: self.decorator.styledRecipientInfo(with: recipient.email)) + } + case (.searchEmails, 2): return indexPath.row == 0 ? DividerCellNode() : self.enableGoogleContactsNode() default: @@ -870,7 +882,7 @@ extension ComposeViewController { return recipient } - let newRecipient = ComposeMessageRecipient(email: text, state: decorator.recipientIdleState) + let newRecipient = ComposeMessageRecipient(email: text, name: nil, state: decorator.recipientIdleState) let indexOfRecipient: Int if let index = contextToSend.recipients.firstIndex(where: { $0.email == newRecipient.email }) { @@ -981,6 +993,7 @@ extension ComposeViewController { let composeRecipient = ComposeMessageRecipient( email: recipient.email, + name: recipient.name, state: state, keyState: recipient.keyState ) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift b/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift index 9df53b687..866c62a21 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift @@ -12,8 +12,8 @@ struct ComposeMessageInput: Equatable { static let empty = ComposeMessageInput(type: .idle) struct MessageQuoteInfo: Equatable { - let recipients: [String] - let sender: String? + let recipients: [MessageRecipient] + let sender: MessageRecipient? let subject: String? let mime: Data? let sentDate: Date @@ -30,7 +30,7 @@ struct ComposeMessageInput: Equatable { let type: InputType - var quoteRecipients: [String] { + var quoteRecipients: [MessageRecipient] { guard case .reply(let info) = type else { return [] } diff --git a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift index aeb261c4c..2d996af8c 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift @@ -74,6 +74,23 @@ struct ComposeViewDecorator { ) } + func styledRecipientInfo(with email: String, name: String) -> LabelCellNode.Input { + LabelCellNode.Input( + title: name.attributed( + .medium(17), + color: .mainTextColor.withAlphaComponent(0.8), + alignment: .left + ), + text: email.attributed( + .regular(15), + color: .mainTextColor.withAlphaComponent(0.5), + alignment: .left + ), + insets: .deviceSpecificTextInsets(top: 8, bottom: 8), + spacing: 0 + ) + } + func styledMessage(with text: String) -> NSAttributedString { text.attributed(.regular(17)) } @@ -91,7 +108,7 @@ struct ComposeViewDecorator { dateFormatter.timeStyle = .short let time = dateFormatter.string(from: info.sentDate) - let from = info.sender ?? "unknown sender" + let from = info.sender?.email ?? "unknown sender" let text: String = "\n\n" + "compose_quote_from".localizeWithArguments(date, time, from) @@ -123,9 +140,9 @@ struct ComposeViewDecorator { color: UIColor, imageName: String ) -> MessagePasswordCellNode.Input { - .init( - text: text.attributed(.regular(14), color: color), - color: color, + .init( + text: text.attributed(.regular(14), color: color), + color: color, image: UIImage(systemName: imageName)?.tinted(color) ) } diff --git a/FlowCrypt/Controllers/Inbox/InboxRenderable.swift b/FlowCrypt/Controllers/Inbox/InboxRenderable.swift index 4ca7f84ee..0dc819095 100644 --- a/FlowCrypt/Controllers/Inbox/InboxRenderable.swift +++ b/FlowCrypt/Controllers/Inbox/InboxRenderable.swift @@ -37,7 +37,7 @@ extension InboxRenderable { extension InboxRenderable { init(message: Message) { - self.title = message.sender ?? "message_unknown_sender".localized + self.title = message.sender?.displayName ?? "message_unknown_sender".localized self.messageCount = 1 self.subtitle = message.subject ?? "message_missed_subject".localized self.dateString = DateFormatter().formatDate(message.date) @@ -78,8 +78,7 @@ extension InboxRenderable { } else { return thread.messages - .compactMap(\.sender) - .compactMap { $0.components(separatedBy: "@").first } + .compactMap(\.sender?.displayName) .unique() .joined(separator: ",") } diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index 5db52970c..6f797ef3c 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -11,7 +11,7 @@ import UIKit extension ThreadMessageInfoCellNode.Input { init(threadMessage: ThreadDetailsViewController.Input) { - let sender = threadMessage.rawMessage.sender ?? "message_unknown_sender".localized + let sender = threadMessage.rawMessage.sender?.displayName ?? "message_unknown_sender".localized let recipientPrefix = "to".localized let recipientsList = threadMessage.rawMessage .allRecipients @@ -34,7 +34,7 @@ extension ThreadMessageInfoCellNode.Input { signatureBadge: makeSignatureBadge(threadMessage), sender: .text(from: sender, style: style, color: .label), recipientLabel: .text(from: recipientLabel, style: style, color: .secondaryLabel), - recipients: threadMessage.rawMessage.recipients.map(\.rawString), + recipients: threadMessage.rawMessage.to.map(\.rawString), ccRecipients: threadMessage.rawMessage.cc.map(\.rawString), bccRecipients: threadMessage.rawMessage.bcc.map(\.rawString), date: .text(from: date, style: style, color: dateColor), diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift index 2a638a9b7..def6577cc 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift @@ -217,14 +217,13 @@ extension ThreadDetailsViewController { else { return } let sender = [input.rawMessage.sender].compactMap { $0 } - let recipients: [String] = { + let recipients: [MessageRecipient] = { switch quoteType { case .reply: return sender case .replyAll: - let recipientEmails = input.rawMessage.recipients.map(\.email) - let allRecipients = (recipientEmails + sender).unique() - let filteredRecipients = allRecipients.filter { $0 != appContext.user.email } + let allRecipients = (input.rawMessage.to + sender).unique() + let filteredRecipients = allRecipients.filter { $0.email != appContext.user.email } return filteredRecipients.isEmpty ? sender : filteredRecipients case .forward: return [] @@ -426,7 +425,7 @@ extension ThreadDetailsViewController { let sender = input[indexPath.section-1].rawMessage.sender let processedMessage = try await messageService.decryptAndProcessMessage( mime: rawMimeData, - sender: sender, + sender: sender?.email, onlyLocalKeys: false ) handleReceived(message: processedMessage, at: indexPath) diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift index 4b7444eed..2a3ab5196 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift @@ -55,7 +55,7 @@ final class UserContactsProvider { var readMask: String { switch self { - case .contacts, .other: return "emailAddresses" + case .contacts, .other: return "names,emailAddresses" } } } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 294112299..cc3a6f902 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -96,7 +96,7 @@ final class MessageService { ) return try await decryptAndProcessMessage( mime: rawMimeData, - sender: input.sender, + sender: input.sender?.email, onlyLocalKeys: onlyLocalKeys ) } diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Imap+MessagesList.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Imap+MessagesList.swift index bb9a574e0..5fcd279be 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Imap+MessagesList.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Imap+MessagesList.swift @@ -49,10 +49,15 @@ extension Message { init(imapMessage: MCOIMAPMessage) { // swiftlint:disable compiler_protocol_init let labels = Array(arrayLiteral: imapMessage.flags).map(MessageLabelType.init).map(MessageLabel.init) + var sender: MessageRecipient? + if let senderString = imapMessage.header.from ?? imapMessage.header.sender { + sender = MessageRecipient(senderString.nonEncodedRFC822String()) + } + self.init( identifier: Identifier(intId: Int(imapMessage.uid)), date: imapMessage.header.date, - sender: imapMessage.header.from.mailbox ?? imapMessage.header.sender.mailbox, + sender: sender, subject: imapMessage.header.subject, size: Int(imapMessage.size), labels: labels, diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift index ceef163e2..cba4cea31 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift @@ -13,8 +13,8 @@ import GoogleAPIClientForREST_Gmail struct Message: Hashable { let identifier: Identifier let date: Date - let sender: String? - let recipients: [MessageRecipient] + let sender: MessageRecipient? + let to: [MessageRecipient] let cc: [MessageRecipient] let bcc: [MessageRecipient] let subject: String? @@ -41,7 +41,7 @@ struct Message: Hashable { init( identifier: Identifier, date: Date, - sender: String?, + sender: MessageRecipient?, subject: String?, size: Int?, labels: [MessageLabel], @@ -49,7 +49,7 @@ struct Message: Hashable { threadId: String? = nil, draftIdentifier: String? = nil, raw: String? = nil, - recipient: String? = nil, + to: String? = nil, cc: String? = nil, bcc: String? = nil ) { @@ -63,7 +63,7 @@ struct Message: Hashable { self.threadId = threadId self.draftIdentifier = draftIdentifier self.raw = raw - self.recipients = Message.parseRecipients(recipient) + self.to = Message.parseRecipients(to) self.cc = Message.parseRecipients(cc) self.bcc = Message.parseRecipients(bcc) } @@ -98,7 +98,7 @@ extension Message { } var allRecipients: [MessageRecipient] { - [recipients, cc, bcc].flatMap { $0 } + [to, cc, bcc].flatMap { $0 } } } @@ -111,35 +111,3 @@ struct Identifier: Equatable, Hashable { self.intId = intId } } - -struct MessageRecipient: Hashable { - let name: String? - let email: String - - init(_ string: String) { - let parts = string.components(separatedBy: " ") - - guard parts.count > 1, let email = parts.last else { - self.name = nil - self.email = string - return - } - - self.email = email.filter { !["<", ">"].contains($0) } - let name = string - .replacingOccurrences(of: email, with: "") - .replacingOccurrences(of: "\"", with: "") - .trimmingCharacters(in: .whitespaces) - self.name = name == self.email ? nil : name - } -} - -extension MessageRecipient { - var displayName: String { - name?.components(separatedBy: " ").first ?? - email.components(separatedBy: "@").first ?? - "unknown" - } - - var rawString: (String?, String) { (name, email) } -} diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift new file mode 100644 index 000000000..92dac5ebc --- /dev/null +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift @@ -0,0 +1,41 @@ +// +// MessageRecipient.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 18/02/22 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct MessageRecipient: RecipientBase, Hashable { + let name: String? + let email: String + + init(_ string: String) { + let parts = string.components(separatedBy: " ") + + guard parts.count > 1, let email = parts.last else { + self.name = nil + self.email = string + return + } + + self.email = email.filter { !["<", ">"].contains($0) } + let name = string + .replacingOccurrences(of: email, with: "") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespaces) + self.name = name == self.email ? nil : name + } +} + +extension MessageRecipient { + var displayName: String { + name?.components(separatedBy: " ").first ?? + email.components(separatedBy: "@").first ?? + "unknown" + } + + var rawString: (String?, String) { (name, email) } +} diff --git a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift index 7ea744222..15ae02a6d 100644 --- a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift @@ -145,30 +145,27 @@ extension Message { let labelTypes: [MessageLabelType] = message.labelIds?.map(MessageLabelType.init) ?? [] let labels = labelTypes.map(MessageLabel.init) - var sender: String? + var sender: MessageRecipient? var subject: String? - var recipient: String? + var to: String? var cc: String? var bcc: String? messageHeaders.compactMap { $0 }.forEach { - guard let name = $0.name?.lowercased() else { return } - let value = $0.value + guard let name = $0.name?.lowercased(), + let value = $0.value + else { return } + switch name { - case .from: sender = value + case .from: sender = MessageRecipient(value) case .subject: subject = value - case .to: recipient = value + case .to: to = value case .cc: cc = value case .bcc: bcc = value default: break } } - // TODO: - Tom 3 - // Gmail returns sender string as "Google security " - // slice it to previous format, like "googleaccount-noreply@gmail.com" - sender = sender?.slice(from: "<", to: ">") ?? sender - self.init( identifier: Identifier(stringId: identifier), // Should be divided by 1000, because Date(timeIntervalSince1970:) expects seconds @@ -182,7 +179,7 @@ extension Message { threadId: message.threadId, draftIdentifier: draftIdentifier, raw: message.raw, - recipient: recipient, + to: to, cc: cc, bcc: bcc ) diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageRecipient.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageRecipient.swift index 18094f5f9..b98d96bc1 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageRecipient.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageRecipient.swift @@ -10,6 +10,7 @@ import Foundation struct ComposeMessageRecipient { let email: String + let name: String? var state: RecipientState var keyState: PubKeyState? } diff --git a/FlowCryptUI/Cell Nodes/LabelCellNode.swift b/FlowCryptUI/Cell Nodes/LabelCellNode.swift index 372afbda8..9917f2606 100644 --- a/FlowCryptUI/Cell Nodes/LabelCellNode.swift +++ b/FlowCryptUI/Cell Nodes/LabelCellNode.swift @@ -14,17 +14,20 @@ public final class LabelCellNode: CellNode { let title: NSAttributedString let text: NSAttributedString let insets: UIEdgeInsets + let spacing: CGFloat let accessibilityIdentifier: String? public init( title: NSAttributedString, text: NSAttributedString, insets: UIEdgeInsets = .deviceSpecificTextInsets(top: 8, bottom: 8), - accessibilityIdentifier: String? + spacing: CGFloat = 4, + accessibilityIdentifier: String? = nil ) { self.title = title self.text = text self.insets = insets + self.spacing = spacing self.accessibilityIdentifier = accessibilityIdentifier } } @@ -47,7 +50,7 @@ public final class LabelCellNode: CellNode { insets: input.insets, child: ASStackLayoutSpec( direction: .vertical, - spacing: 4, + spacing: input.spacing, justifyContent: .start, alignItems: .start, children: [titleNode, textNode] From bd2e381012efa46c099e85a6bdf643266f6d2703 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 21 Feb 2022 23:05:19 +0200 Subject: [PATCH 03/22] Show recipient name when available --- FlowCrypt.xcodeproj/project.pbxproj | 12 -------- .../Compose/ComposeViewController.swift | 23 +++++++------- .../Compose/ComposeViewDecorator.swift | 2 +- .../Controllers/Inbox/InboxRenderable.swift | 6 ++-- .../Threads/ThreadDetailsDecorator.swift | 4 +-- .../CloudContactsProvider.swift | 2 +- .../Model/CloudContact.swift | 29 ------------------ .../Model/MessageRecipient.swift | 30 +++++++++++++++---- .../ComposeMessageRecipient.swift | 2 +- FlowCrypt/Models/Common/RecipientBase.swift | 10 +++++++ .../Services/ComposeMessageServiceTests.swift | 12 ++++---- .../Mocks/ContactsServiceMock.swift | 3 +- 12 files changed, 63 insertions(+), 72 deletions(-) delete mode 100644 FlowCrypt/Functionality/Mail Provider/Contacts Provider/Model/CloudContact.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 3db6ca6cf..78e991665 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -63,7 +63,6 @@ 51074E6427BD0C5800FBB124 /* RecipientToggleButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51074E6327BD0C5800FBB124 /* RecipientToggleButtonNode.swift */; }; 5109A77C272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5109A77B272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift */; }; 510BB63527BE92CC00B1011F /* RecipientBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63427BE92CC00B1011F /* RecipientBase.swift */; }; - 510BB63827BE9B1300B1011F /* CloudContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63727BE9B1300B1011F /* CloudContact.swift */; }; 510BB63A27BFD6C900B1011F /* MessageRecipient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63927BFD6C800B1011F /* MessageRecipient.swift */; }; 511D07E12769FBBA0050417B /* MessagePasswordCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D07E02769FBBA0050417B /* MessagePasswordCellNode.swift */; }; 511D07E3276A2DF80050417B /* ButtonWithPaddingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */; }; @@ -508,7 +507,6 @@ 51074E6327BD0C5800FBB124 /* RecipientToggleButtonNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientToggleButtonNode.swift; sourceTree = ""; }; 5109A77B272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftAlignedCollectionViewFlowLayout.swift; sourceTree = ""; }; 510BB63427BE92CC00B1011F /* RecipientBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientBase.swift; sourceTree = ""; }; - 510BB63727BE9B1300B1011F /* CloudContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudContact.swift; sourceTree = ""; }; 510BB63927BFD6C800B1011F /* MessageRecipient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRecipient.swift; sourceTree = ""; }; 511D07E02769FBBA0050417B /* MessagePasswordCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePasswordCellNode.swift; sourceTree = ""; }; 511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonWithPaddingNode.swift; sourceTree = ""; }; @@ -1060,14 +1058,6 @@ path = SignIn; sourceTree = ""; }; - 510BB63627BE9AEB00B1011F /* Model */ = { - isa = PBXGroup; - children = ( - 510BB63727BE9B1300B1011F /* CloudContact.swift */, - ); - path = Model; - sourceTree = ""; - }; 5161D87D27A488BD00130518 /* Contacts Service */ = { isa = PBXGroup; children = ( @@ -1659,7 +1649,6 @@ 9FFC7E60260C948200282FCE /* Contacts Provider */ = { isa = PBXGroup; children = ( - 510BB63627BE9AEB00B1011F /* Model */, 9FFC7E59260C946000282FCE /* CloudContactsProvider.swift */, D29AFFF82409767F00C1387D /* GoogleContactsResponse.swift */, ); @@ -2667,7 +2656,6 @@ 9F3EF32F23B172D300FA0CEF /* SearchViewController.swift in Sources */, F191F621272511790053833E /* BlurViewController.swift in Sources */, D2F6D147243506DA00DB4065 /* MailSettingsCredentials.swift in Sources */, - 510BB63827BE9B1300B1011F /* CloudContact.swift in Sources */, 9F9361A52573CE260009912F /* MessageProvider.swift in Sources */, 32DCA2BB910FA1B19BA8B328 /* Imap.swift in Sources */, D274724424FD1932006BA6EF /* FolderRealmObject.swift in Sources */, diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 5c194e25f..743039bdf 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -44,7 +44,7 @@ final class ComposeViewController: TableNodeViewController { } private enum State { - case main, searchEmails([RecipientBase]) + case main, searchEmails([MessageRecipient]) } private enum Section: Hashable { @@ -640,8 +640,7 @@ extension ComposeViewController: ASTableDelegate, ASTableDataSource { case let (.searchEmails(recipients), .searchResults): guard indexPath.row > 0 else { return DividerCellNode() } guard recipients.isNotEmpty else { return self.noSearchResultsNode() } - - let recipient = recipients[indexPath.row-1] + guard let recipient = recipients[safe: indexPath.row-1] else { return ASCellNode() } if let name = recipient.name { return LabelCellNode(input: self.decorator.styledRecipientInfo(with: recipient.email, name: name)) @@ -662,8 +661,8 @@ extension ComposeViewController: ASTableDelegate, ASTableDataSource { switch section { case .searchResults: - let selectedEmail = recipients[safe: indexPath.row-1]?.email - handleEndEditingAction(with: selectedEmail, for: recipientType) + let recipient = recipients[safe: indexPath.row-1] + handleEndEditingAction(with: recipient?.email, name: recipient?.name, for: recipientType) case .contacts: askForContactsPermission() default: @@ -941,9 +940,9 @@ extension ComposeViewController { } } - private func handleEndEditingAction(with text: String?, for recipientType: RecipientType) { + private func handleEndEditingAction(with email: String?, name: String? = nil, for recipientType: RecipientType) { guard shouldEvaluateRecipientInput, - let text = text, text.isNotEmpty + let email = email, email.isNotEmpty else { return } let recipients = contextToSend.recipients(type: recipientType) @@ -962,7 +961,7 @@ extension ComposeViewController { contextToSend.set(recipients: idleRecipients, for: recipientType) - let newRecipient = ComposeMessageRecipient(email: text, name: nil, type: recipientType, state: decorator.recipientIdleState) + let newRecipient = ComposeMessageRecipient(email: email, name: name, type: recipientType, state: decorator.recipientIdleState) let indexOfRecipient: Int let indexPath = recipientsIndexPath(type: recipientType, part: .list) @@ -1066,10 +1065,12 @@ extension ComposeViewController { private func searchEmail(with query: String) { Task { do { - let localRecipients = try contactsService.searchLocalContacts(query: query) let cloudRecipients = try await service.searchContacts(query: query) - let recipients = localRecipients + cloudRecipients - // TODO: Add check for unique + let cloudEmails = cloudRecipients.map(\.email) + let localRecipients = try contactsService.searchLocalContacts(query: query).filter { !cloudEmails.contains($0.email) } + let recipients = (localRecipients + cloudRecipients) + .map(MessageRecipient.init) + .unique() updateState(with: .searchEmails(recipients)) } catch { showAlert(message: error.localizedDescription) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift index 205b57528..2725b50f0 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift @@ -289,7 +289,7 @@ extension ComposeViewDecorator { extension RecipientEmailsCellNode.Input { init(_ recipient: ComposeMessageRecipient) { self.init( - email: recipient.email.lowercased().attributed( + email: recipient.displayName.attributed( .regular(17), color: recipient.state.textColor, alignment: .left diff --git a/FlowCrypt/Controllers/Inbox/InboxRenderable.swift b/FlowCrypt/Controllers/Inbox/InboxRenderable.swift index 4e2e7b61b..f85b7d7b2 100644 --- a/FlowCrypt/Controllers/Inbox/InboxRenderable.swift +++ b/FlowCrypt/Controllers/Inbox/InboxRenderable.swift @@ -37,7 +37,7 @@ extension InboxRenderable { extension InboxRenderable { init(message: Message) { - self.title = message.sender?.displayName ?? "message_unknown_sender".localized + self.title = message.sender?.shortName ?? "message_unknown_sender".localized self.messageCount = 1 self.subtitle = message.subject ?? "message_missing_subject".localized self.dateString = DateFormatter().formatDate(message.date) @@ -71,14 +71,14 @@ extension InboxRenderable { if folderPath == MessageLabelType.sent.value { let recipients = thread.messages .flatMap(\.allRecipients) - .map(\.displayName) + .map(\.shortName) .unique() .joined(separator: ", ") return "To: \(recipients)" } else { return thread.messages - .compactMap(\.sender?.displayName) + .compactMap(\.sender?.shortName) .unique() .joined(separator: ",") } diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index 6f797ef3c..b54339592 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -11,11 +11,11 @@ import UIKit extension ThreadMessageInfoCellNode.Input { init(threadMessage: ThreadDetailsViewController.Input) { - let sender = threadMessage.rawMessage.sender?.displayName ?? "message_unknown_sender".localized + let sender = threadMessage.rawMessage.sender?.shortName ?? "message_unknown_sender".localized let recipientPrefix = "to".localized let recipientsList = threadMessage.rawMessage .allRecipients - .map(\.displayName) + .map(\.shortName) .joined(separator: ", ") let recipientLabel = [recipientPrefix, recipientsList].joined(separator: " ") let date = DateFormatter().formatDate(threadMessage.rawMessage.date) diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift index 2a3ab5196..c330b9b11 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift @@ -115,7 +115,7 @@ extension UserContactsProvider { return continuation.resume(throwing: CloudContactsProviderError.failedToParseData(data)) } - let recipients = contacts.compactMap(\.person).compactMap(CloudContact.init) + let recipients = contacts.compactMap(\.person).compactMap(MessageRecipient.init) return continuation.resume(returning: recipients) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/Model/CloudContact.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/Model/CloudContact.swift deleted file mode 100644 index ab2709626..000000000 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/Model/CloudContact.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// CloudContact.swift -// FlowCrypt -// -// Created by Roma Sosnovsky on 17/02/22 -// Copyright © 2017-present FlowCrypt a. s. All rights reserved. -// - -import Foundation -import GoogleAPIClientForREST_PeopleService - -struct CloudContact: RecipientBase, Hashable { - let email: String - let name: String? -} - -extension CloudContact { - init?(person: GTLRPeopleService_Person) { - guard let email = person.emailAddresses?.first?.value else { return nil } - - self.email = email - - if let name = person.names?.first { - self.name = [name.givenName, name.familyName].compactMap { $0 }.joined(separator: " ") - } else { - self.name = nil - } - } -} diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift index 92dac5ebc..f4b28cc3a 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift @@ -7,6 +7,7 @@ // import Foundation +import GoogleAPIClientForREST_PeopleService struct MessageRecipient: RecipientBase, Hashable { let name: String? @@ -28,14 +29,33 @@ struct MessageRecipient: RecipientBase, Hashable { .trimmingCharacters(in: .whitespaces) self.name = name == self.email ? nil : name } + + init?(person: GTLRPeopleService_Person) { + guard let email = person.emailAddresses?.first?.value else { return nil } + + self.email = email + + if let name = person.names?.first { + self.name = [name.givenName, name.familyName].compactMap { $0 }.joined(separator: " ") + } else { + self.name = nil + } + } + + init(recipient: RecipientBase) { + self.name = recipient.name + self.email = recipient.email + } } -extension MessageRecipient { - var displayName: String { - name?.components(separatedBy: " ").first ?? - email.components(separatedBy: "@").first ?? - "unknown" +extension MessageRecipient: Comparable { + static func < (lhs: MessageRecipient, rhs: MessageRecipient) -> Bool { + guard let name1 = lhs.name else { return true } + guard let name2 = rhs.name else { return false } + return name1 < name2 } +} +extension MessageRecipient { var rawString: (String?, String) { (name, email) } } diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageRecipient.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageRecipient.swift index af1a4e099..d4b5f53ea 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageRecipient.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageRecipient.swift @@ -8,7 +8,7 @@ import Foundation -struct ComposeMessageRecipient { +struct ComposeMessageRecipient: RecipientBase { let email: String let name: String? let type: RecipientType diff --git a/FlowCrypt/Models/Common/RecipientBase.swift b/FlowCrypt/Models/Common/RecipientBase.swift index cb26d1adc..a801ccb8d 100644 --- a/FlowCrypt/Models/Common/RecipientBase.swift +++ b/FlowCrypt/Models/Common/RecipientBase.swift @@ -18,4 +18,14 @@ extension RecipientBase { guard let name = name else { return email } return "\(name) <\(email)>" } + + var shortName: String { + name?.components(separatedBy: " ").first ?? + email.components(separatedBy: "@").first ?? + "unknown" + } + + var displayName: String { + name ?? email + } } diff --git a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift index c38753bc7..aed81b326 100644 --- a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift +++ b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift @@ -16,9 +16,9 @@ class ComposeMessageServiceTests: XCTestCase { var sut: ComposeMessageService! let recipients: [ComposeMessageRecipient] = [ - ComposeMessageRecipient(email: "test@gmail.com", type: .to, state: recipientIdleState), - ComposeMessageRecipient(email: "test2@gmail.com", type: .to, state: recipientIdleState), - ComposeMessageRecipient(email: "test3@gmail.com", type: .to, state: recipientIdleState) + ComposeMessageRecipient(email: "test@gmail.com", name: "Test", type: .to, state: recipientIdleState), + ComposeMessageRecipient(email: "test2@gmail.com", name: nil, type: .to, state: recipientIdleState), + ComposeMessageRecipient(email: "test3@gmail.com", name: nil, type: .to, state: recipientIdleState) ] let validKeyDetails = EncryptedStorageMock.createFakeKeyDetails(expiration: nil) let keypair = Keypair( @@ -77,9 +77,9 @@ class ComposeMessageServiceTests: XCTestCase { func testValidateMessageInputWithWhitespaceRecipients() async { let recipients: [ComposeMessageRecipient] = [ - ComposeMessageRecipient(email: " ", type: .to, state: recipientIdleState), - ComposeMessageRecipient(email: " ", type: .to, state: recipientIdleState), - ComposeMessageRecipient(email: "sdfff", type: .to, state: recipientIdleState) + ComposeMessageRecipient(email: " ", name: nil, type: .to, state: recipientIdleState), + ComposeMessageRecipient(email: " ", name: nil, type: .to, state: recipientIdleState), + ComposeMessageRecipient(email: "sdfff", name: nil, type: .to, state: recipientIdleState) ] do { _ = try await sut.validateAndProduceSendableMsg( diff --git a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift index 75dddc909..fe653169a 100644 --- a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift +++ b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift @@ -38,7 +38,8 @@ final class ContactsServiceMock: ContactsServiceType { fatalError() } } - func searchLocalContacts(query: String) -> [String] { [] } + + func searchLocalContacts(query: String) throws -> [RecipientBase] { [] } func removePubKey(with fingerprint: String, for email: String) {} } From 7d10b97fca11fc8764ede04b8ee919405f69d679 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 22 Feb 2022 13:36:38 +0200 Subject: [PATCH 04/22] Update recipients search logic --- .../Compose/ComposeViewController.swift | 6 +++-- .../Model/MessageRecipient.swift | 22 ++++++++++++++----- .../ComposeMessageContext.swift | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 743039bdf..a59fd17f0 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -1066,11 +1066,13 @@ extension ComposeViewController { Task { do { let cloudRecipients = try await service.searchContacts(query: query) - let cloudEmails = cloudRecipients.map(\.email) - let localRecipients = try contactsService.searchLocalContacts(query: query).filter { !cloudEmails.contains($0.email) } + let localRecipients = try contactsService.searchLocalContacts(query: query) + let recipients = (localRecipients + cloudRecipients) .map(MessageRecipient.init) .unique() + .sorted() + updateState(with: .searchEmails(recipients)) } catch { showAlert(message: error.localizedDescription) diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift index f4b28cc3a..50c5c177f 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift @@ -9,7 +9,7 @@ import Foundation import GoogleAPIClientForREST_PeopleService -struct MessageRecipient: RecipientBase, Hashable { +struct MessageRecipient: RecipientBase { let name: String? let email: String @@ -48,14 +48,26 @@ struct MessageRecipient: RecipientBase, Hashable { } } +extension MessageRecipient { + var rawString: (String?, String) { (name, email) } +} + extension MessageRecipient: Comparable { static func < (lhs: MessageRecipient, rhs: MessageRecipient) -> Bool { - guard let name1 = lhs.name else { return true } - guard let name2 = rhs.name else { return false } + guard let name1 = lhs.name else { return false } + guard let name2 = rhs.name else { return true } return name1 < name2 } } -extension MessageRecipient { - var rawString: (String?, String) { (name, email) } +extension MessageRecipient: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(email) + } +} + +extension MessageRecipient: Equatable { + static func == (lhs: MessageRecipient, rhs: MessageRecipient) -> Bool { + lhs.email == rhs.email + } } diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageContext.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageContext.swift index 9feb95d76..859d9ab79 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageContext.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageContext.swift @@ -60,7 +60,7 @@ extension ComposeMessageContext { } func recipientEmails(type: RecipientType) -> [String] { - recipients(type: type).map(\.email) + recipients(type: type).map(\.formatted) } func recipient(at index: Int, type: RecipientType) -> ComposeMessageRecipient? { From 29822125d146d2a7e1d3580a303b9f138a63e0e4 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 22 Feb 2022 22:59:24 +0200 Subject: [PATCH 05/22] merge MessageRecipient and Recipient --- FlowCrypt.xcodeproj/project.pbxproj | 4 - .../Compose/ComposeViewController.swift | 13 ++-- .../Compose/ComposeViewControllerInput.swift | 6 +- .../Threads/ThreadDetailsViewController.swift | 4 +- .../CloudContactsProvider.swift | 2 +- .../Message Provider/MessageService.swift | 14 ++-- .../Imap+MessagesList.swift | 4 +- .../MessagesList Provider/Model/Message.swift | 16 ++-- .../Model/MessageRecipient.swift | 73 ------------------- .../Threads/MessagesThreadProvider.swift | 4 +- .../ComposeMessageService.swift | 3 +- .../Contacts Service/ContactsService.swift | 10 +-- .../Models/RecipientWithSortedPubKeys.swift | 6 +- .../Remote Pub Key Services/PubLookup.swift | 12 +-- FlowCrypt/Models/Common/Recipient.swift | 65 ++++++++++++++++- .../Services/ComposeMessageServiceTests.swift | 4 +- Gemfile.lock | 2 +- 17 files changed, 113 insertions(+), 129 deletions(-) delete mode 100644 FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 859a877c0..d7b338e17 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -63,7 +63,6 @@ 51074E6427BD0C5800FBB124 /* RecipientToggleButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51074E6327BD0C5800FBB124 /* RecipientToggleButtonNode.swift */; }; 5109A77C272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5109A77B272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift */; }; 510BB63527BE92CC00B1011F /* RecipientBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63427BE92CC00B1011F /* RecipientBase.swift */; }; - 510BB63A27BFD6C900B1011F /* MessageRecipient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BB63927BFD6C800B1011F /* MessageRecipient.swift */; }; 511D07E12769FBBA0050417B /* MessagePasswordCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D07E02769FBBA0050417B /* MessagePasswordCellNode.swift */; }; 511D07E3276A2DF80050417B /* ButtonWithPaddingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */; }; 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */ = {isa = PBXBuildFile; productRef = 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */; }; @@ -507,7 +506,6 @@ 51074E6327BD0C5800FBB124 /* RecipientToggleButtonNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientToggleButtonNode.swift; sourceTree = ""; }; 5109A77B272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftAlignedCollectionViewFlowLayout.swift; sourceTree = ""; }; 510BB63427BE92CC00B1011F /* RecipientBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientBase.swift; sourceTree = ""; }; - 510BB63927BFD6C800B1011F /* MessageRecipient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRecipient.swift; sourceTree = ""; }; 511D07E02769FBBA0050417B /* MessagePasswordCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePasswordCellNode.swift; sourceTree = ""; }; 511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonWithPaddingNode.swift; sourceTree = ""; }; 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailViewController.swift; sourceTree = ""; }; @@ -1621,7 +1619,6 @@ 9FE1B39F2565B0CD00D6D086 /* Message.swift */, 9F5C2A76257D705100DE9B4B /* MessageLabel.swift */, 51938DC0274CC291007AD57B /* MessageQuoteType.swift */, - 510BB63927BFD6C800B1011F /* MessageRecipient.swift */, ); path = Model; sourceTree = ""; @@ -2779,7 +2776,6 @@ D2F6D1332433753100DB4065 /* IMAPConnectionParameters.swift in Sources */, 9F003DB625EA92BC00EB38C0 /* LogOutHandler.swift in Sources */, D20D3C6E2520AB3900D4AA9A /* BackupViewDecorator.swift in Sources */, - 510BB63A27BFD6C900B1011F /* MessageRecipient.swift in Sources */, 9F268891237DC55600428A94 /* SetupManuallyImportKeyViewController.swift in Sources */, D2E26F7024F266F300612AF1 /* ContactDetailViewController.swift in Sources */, 5A39F43F239EE7D2001F4607 /* SegmentedViewController.swift in Sources */, diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index a59fd17f0..76c6698ee 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -44,7 +44,7 @@ final class ComposeViewController: TableNodeViewController { } private enum State { - case main, searchEmails([MessageRecipient]) + case main, searchEmails([Recipient]) } private enum Section: Hashable { @@ -1067,9 +1067,9 @@ extension ComposeViewController { do { let cloudRecipients = try await service.searchContacts(query: query) let localRecipients = try contactsService.searchLocalContacts(query: query) - + let recipients = (localRecipients + cloudRecipients) - .map(MessageRecipient.init) + .map(Recipient.init) .unique() .sorted() @@ -1095,7 +1095,8 @@ extension ComposeViewController { handleEvaluation(for: contact) } - let contactWithFetchedKeys = try await service.fetchContact(with: recipient.email) + let recipient = Recipient(recipient: recipient) + let contactWithFetchedKeys = try await service.fetchContact(recipient) handleEvaluation(for: contactWithFetchedKeys) } catch { handleEvaluation(error: error, with: recipient.email) @@ -1573,7 +1574,7 @@ private actor ServiceActor { return try await contactsService.findLocalContact(with: email) } - func fetchContact(with email: String) async throws -> RecipientWithSortedPubKeys { - return try await contactsService.fetchContact(with: email) + func fetchContact(_ contact: Recipient) async throws -> RecipientWithSortedPubKeys { + return try await contactsService.fetchContact(contact) } } diff --git a/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift b/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift index 866c62a21..930eb1dc5 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift @@ -12,8 +12,8 @@ struct ComposeMessageInput: Equatable { static let empty = ComposeMessageInput(type: .idle) struct MessageQuoteInfo: Equatable { - let recipients: [MessageRecipient] - let sender: MessageRecipient? + let recipients: [Recipient] + let sender: Recipient? let subject: String? let mime: Data? let sentDate: Date @@ -30,7 +30,7 @@ struct ComposeMessageInput: Equatable { let type: InputType - var quoteRecipients: [MessageRecipient] { + var quoteRecipients: [Recipient] { guard case .reply(let info) = type else { return [] } diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift index e4f87c4ef..01fa46985 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift @@ -225,7 +225,7 @@ extension ThreadDetailsViewController { else { return } let sender = [input.rawMessage.sender].compactMap { $0 } - let recipients: [MessageRecipient] = { + let recipients: [Recipient] = { switch quoteType { case .reply: return sender @@ -433,7 +433,7 @@ extension ThreadDetailsViewController { let sender = input[indexPath.section-1].rawMessage.sender let processedMessage = try await messageService.decryptAndProcessMessage( mime: rawMimeData, - sender: sender?.email, + sender: sender, onlyLocalKeys: false ) handleReceived(message: processedMessage, at: indexPath) diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift index c330b9b11..2c1e187d9 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift @@ -115,7 +115,7 @@ extension UserContactsProvider { return continuation.resume(throwing: CloudContactsProviderError.failedToParseData(data)) } - let recipients = contacts.compactMap(\.person).compactMap(MessageRecipient.init) + let recipients = contacts.compactMap(\.person).compactMap(Recipient.init) return continuation.resume(returning: recipients) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index cc3a6f902..c03eae452 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -96,14 +96,14 @@ final class MessageService { ) return try await decryptAndProcessMessage( mime: rawMimeData, - sender: input.sender?.email, + sender: input.sender, onlyLocalKeys: onlyLocalKeys ) } func decryptAndProcessMessage( mime rawMimeData: Data, - sender: String?, + sender: Recipient?, onlyLocalKeys: Bool ) async throws -> ProcessedMessage { let keys = try await keyService.getPrvKeyInfo() @@ -149,7 +149,7 @@ final class MessageService { private func processMessage( rawMimeData: Data, - sender: String?, + sender: Recipient?, with decrypted: CoreRes.ParseDecryptMsg, keys: [PrvKeyInfo] ) async throws -> ProcessedMessage { @@ -229,13 +229,13 @@ final class MessageService { // MARK: - Message verification extension MessageService { - private func fetchVerificationPubKeys(for email: String?, onlyLocal: Bool) async throws -> [String] { - guard let email = email else { return [] } + private func fetchVerificationPubKeys(for sender: Recipient?, onlyLocal: Bool) async throws -> [String] { + guard let sender = sender else { return [] } - let pubKeys = try contactsService.retrievePubKeys(for: email) + let pubKeys = try contactsService.retrievePubKeys(for: sender.email) if pubKeys.isNotEmpty || onlyLocal { return pubKeys } - guard let contact = try? await contactsService.fetchContact(with: email) + guard let contact = try? await contactsService.fetchContact(sender) else { return [] } return contact.pubKeys.map(\.armored) diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Imap+MessagesList.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Imap+MessagesList.swift index 5fcd279be..2f219241b 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Imap+MessagesList.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Imap+MessagesList.swift @@ -49,9 +49,9 @@ extension Message { init(imapMessage: MCOIMAPMessage) { // swiftlint:disable compiler_protocol_init let labels = Array(arrayLiteral: imapMessage.flags).map(MessageLabelType.init).map(MessageLabel.init) - var sender: MessageRecipient? + var sender: Recipient? if let senderString = imapMessage.header.from ?? imapMessage.header.sender { - sender = MessageRecipient(senderString.nonEncodedRFC822String()) + sender = Recipient(senderString.nonEncodedRFC822String()) } self.init( diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift index cba4cea31..01adfe054 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift @@ -13,10 +13,10 @@ import GoogleAPIClientForREST_Gmail struct Message: Hashable { let identifier: Identifier let date: Date - let sender: MessageRecipient? - let to: [MessageRecipient] - let cc: [MessageRecipient] - let bcc: [MessageRecipient] + let sender: Recipient? + let to: [Recipient] + let cc: [Recipient] + let bcc: [Recipient] let subject: String? let size: Int? let attachmentIds: [String] @@ -41,7 +41,7 @@ struct Message: Hashable { init( identifier: Identifier, date: Date, - sender: MessageRecipient?, + sender: Recipient?, subject: String?, size: Int?, labels: [MessageLabel], @@ -80,8 +80,8 @@ extension Message: Equatable, Comparable { } extension Message { - static func parseRecipients(_ string: String?) -> [MessageRecipient] { - string?.components(separatedBy: ", ").map(MessageRecipient.init) ?? [] + static func parseRecipients(_ string: String?) -> [Recipient] { + string?.components(separatedBy: ", ").map(Recipient.init) ?? [] } } @@ -97,7 +97,7 @@ extension Message { return copy } - var allRecipients: [MessageRecipient] { + var allRecipients: [Recipient] { [to, cc, bcc].flatMap { $0 } } } diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift deleted file mode 100644 index 50c5c177f..000000000 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageRecipient.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// MessageRecipient.swift -// FlowCrypt -// -// Created by Roma Sosnovsky on 18/02/22 -// Copyright © 2017-present FlowCrypt a. s. All rights reserved. -// - -import Foundation -import GoogleAPIClientForREST_PeopleService - -struct MessageRecipient: RecipientBase { - let name: String? - let email: String - - init(_ string: String) { - let parts = string.components(separatedBy: " ") - - guard parts.count > 1, let email = parts.last else { - self.name = nil - self.email = string - return - } - - self.email = email.filter { !["<", ">"].contains($0) } - let name = string - .replacingOccurrences(of: email, with: "") - .replacingOccurrences(of: "\"", with: "") - .trimmingCharacters(in: .whitespaces) - self.name = name == self.email ? nil : name - } - - init?(person: GTLRPeopleService_Person) { - guard let email = person.emailAddresses?.first?.value else { return nil } - - self.email = email - - if let name = person.names?.first { - self.name = [name.givenName, name.familyName].compactMap { $0 }.joined(separator: " ") - } else { - self.name = nil - } - } - - init(recipient: RecipientBase) { - self.name = recipient.name - self.email = recipient.email - } -} - -extension MessageRecipient { - var rawString: (String?, String) { (name, email) } -} - -extension MessageRecipient: Comparable { - static func < (lhs: MessageRecipient, rhs: MessageRecipient) -> Bool { - guard let name1 = lhs.name else { return false } - guard let name2 = rhs.name else { return true } - return name1 < name2 - } -} - -extension MessageRecipient: Hashable { - func hash(into hasher: inout Hasher) { - hasher.combine(email) - } -} - -extension MessageRecipient: Equatable { - static func == (lhs: MessageRecipient, rhs: MessageRecipient) -> Bool { - lhs.email == rhs.email - } -} diff --git a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift index 3bbf3a05d..2724d862c 100644 --- a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift @@ -145,7 +145,7 @@ extension Message { let labelTypes: [MessageLabelType] = message.labelIds?.map(MessageLabelType.init) ?? [] let labels = labelTypes.map(MessageLabel.init) - var sender: MessageRecipient? + var sender: Recipient? var subject: String? var to: String? var cc: String? @@ -157,7 +157,7 @@ extension Message { else { return } switch name { - case .from: sender = MessageRecipient(value) + case .from: sender = Recipient(value) case .subject: subject = value case .to: to = value case .cc: cc = value diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index d07ce7934..c46abdb10 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -152,7 +152,8 @@ final class ComposeMessageService { for recipient in recipients { let armoredPubkeys = try contactsService.retrievePubKeys(for: recipient.email).joined(separator: "\n") let parsed = try await self.core.parseKeys(armoredOrBinary: armoredPubkeys.data()) - recipientsWithKeys.append(RecipientWithSortedPubKeys(email: recipient.email, keyDetails: parsed.keyDetails)) + let recipient = Recipient(recipient: recipient) // TODO: + recipientsWithKeys.append(RecipientWithSortedPubKeys(recipient: recipient, keyDetails: parsed.keyDetails)) } return recipientsWithKeys } diff --git a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift index 772273269..ea11d67c7 100644 --- a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift @@ -19,7 +19,7 @@ protocol ContactsServiceType: PublicKeyProvider, ContactsProviderType { protocol ContactsProviderType { func findLocalContact(with email: String) async throws -> RecipientWithSortedPubKeys? func searchLocalContacts(query: String) throws -> [RecipientBase] - func fetchContact(with email: String) async throws -> RecipientWithSortedPubKeys + func fetchContact(_ contact: Recipient) async throws -> RecipientWithSortedPubKeys } protocol PublicKeyProvider { @@ -51,10 +51,10 @@ extension ContactsService: ContactsProviderType { try localContactsProvider.searchRecipients(query: query) } - func fetchContact(with email: String) async throws -> RecipientWithSortedPubKeys { - let recipient = try await pubLookup.lookup(email: email) - try localContactsProvider.updateKeys(for: recipient) - return recipient + func fetchContact(_ recipient: Recipient) async throws -> RecipientWithSortedPubKeys { + let lookupRecipient = try await pubLookup.lookup(recipient: recipient) + try localContactsProvider.updateKeys(for: lookupRecipient) + return lookupRecipient } } diff --git a/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift b/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift index e8b44c182..5e2a041c1 100644 --- a/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift +++ b/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift @@ -33,9 +33,9 @@ extension RecipientWithSortedPubKeys { } extension RecipientWithSortedPubKeys { - init(email: String, keyDetails: [KeyDetails]) { - self.email = email - self.name = keyDetails.first?.users.first ?? email + init(recipient: Recipient, keyDetails: [KeyDetails]) { + self.email = recipient.email + self.name = recipient.name ?? keyDetails.first?.users.first ?? email self.lastUsed = nil self._pubKeys = keyDetails.map(PubKey.init) } diff --git a/FlowCrypt/Functionality/Services/Remote Pub Key Services/PubLookup.swift b/FlowCrypt/Functionality/Services/Remote Pub Key Services/PubLookup.swift index 5acea53f8..52f6619d5 100644 --- a/FlowCrypt/Functionality/Services/Remote Pub Key Services/PubLookup.swift +++ b/FlowCrypt/Functionality/Services/Remote Pub Key Services/PubLookup.swift @@ -7,7 +7,7 @@ // protocol PubLookupType { - func lookup(email: String) async throws -> RecipientWithSortedPubKeys + func lookup(recipient: Recipient) async throws -> RecipientWithSortedPubKeys } class PubLookup: PubLookupType { @@ -33,14 +33,14 @@ class PubLookup: PubLookupType { self.attesterApi = attesterApi ?? AttesterApi(clientConfiguration: clientConfiguration) } - func lookup(email: String) async throws -> RecipientWithSortedPubKeys { + func lookup(recipient: Recipient) async throws -> RecipientWithSortedPubKeys { let results: [LookupResult] = try await withThrowingTaskGroup(of: LookupResult.self) { tg in var results: [LookupResult] = [] tg.addTask { - LookupResult(keys: try await self.wkd.lookup(email: email), source: .wkd) + LookupResult(keys: try await self.wkd.lookup(email: recipient.email), source: .wkd) } tg.addTask { - LookupResult(keys: try await self.attesterApi.lookup(email: email), source: .attester) + LookupResult(keys: try await self.attesterApi.lookup(email: recipient.email), source: .attester) } for try await result in tg { results.append(result) @@ -56,10 +56,10 @@ class PubLookup: PubLookupType { if !wkdResult.keys.isEmpty { // WKD keys are preferred. The trust level is higher because the recipient // controls the distribution of the keys themselves on their own domain - return RecipientWithSortedPubKeys(email: email, keyDetails: wkdResult.keys) + return RecipientWithSortedPubKeys(recipient: recipient, keyDetails: wkdResult.keys) } // Attester keys are less preferred because they come from less trustworthy source // (the FlowCrypt server) - return RecipientWithSortedPubKeys(email: email, keyDetails: attesterResult.keys) + return RecipientWithSortedPubKeys(recipient: recipient, keyDetails: attesterResult.keys) } } diff --git a/FlowCrypt/Models/Common/Recipient.swift b/FlowCrypt/Models/Common/Recipient.swift index d927fabb8..d82d7449b 100644 --- a/FlowCrypt/Models/Common/Recipient.swift +++ b/FlowCrypt/Models/Common/Recipient.swift @@ -7,12 +7,13 @@ // import Foundation +import GoogleAPIClientForREST_PeopleService struct Recipient: RecipientBase { - var email: String - var name: String? + let email: String + let name: String? var lastUsed: Date? - var pubKeys: [PubKey] + var pubKeys: [PubKey] = [] } extension Recipient { @@ -22,4 +23,62 @@ extension Recipient { self.lastUsed = recipientObject.lastUsed self.pubKeys = recipientObject.pubKeys.map(PubKey.init) } + + init(_ string: String) { + let parts = string.components(separatedBy: " ") + + guard parts.count > 1, let email = parts.last else { + self.name = nil + self.email = string + return + } + + self.email = email.filter { !["<", ">"].contains($0) } + let name = string + .replacingOccurrences(of: email, with: "") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespaces) + self.name = name == self.email ? nil : name + } + + init?(person: GTLRPeopleService_Person) { + guard let email = person.emailAddresses?.first?.value else { return nil } + + self.email = email + + if let name = person.names?.first { + self.name = [name.givenName, name.familyName].compactMap { $0 }.joined(separator: " ") + } else { + self.name = nil + } + } + + init(recipient: RecipientBase) { + self.name = recipient.name + self.email = recipient.email + } +} + +extension Recipient { + var rawString: (String?, String) { (name, email) } +} + +extension Recipient: Comparable { + static func < (lhs: Recipient, rhs: Recipient) -> Bool { + guard let name1 = lhs.name else { return false } + guard let name2 = rhs.name else { return true } + return name1 < name2 + } +} + +extension Recipient: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(email) + } +} + +extension Recipient: Equatable { + static func == (lhs: Recipient, rhs: Recipient) -> Bool { + lhs.email == rhs.email + } } diff --git a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift index aed81b326..331f62db5 100644 --- a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift +++ b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift @@ -323,7 +323,7 @@ class ComposeMessageServiceTests: XCTestCase { let expected = SendableMsg( text: message, html: nil, - to: recipients.map(\.email), + to: recipients.map(\.formatted), cc: [], bcc: [], from: email, @@ -396,7 +396,7 @@ class ComposeMessageServiceTests: XCTestCase { let expected = SendableMsg( text: message, html: nil, - to: recipients.map(\.email), + to: recipients.map(\.formatted), cc: [], bcc: [], from: email, diff --git a/Gemfile.lock b/Gemfile.lock index 7cf828032..82db779d1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,7 +17,7 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.555.0) + aws-partitions (1.556.0) aws-sdk-core (3.126.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) From 9f18dc8a35e02f2e0c2d6e274870f94e11a8df5e Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 23 Feb 2022 12:04:19 +0200 Subject: [PATCH 06/22] fix unit tests --- .../Compose/ComposeViewController.swift | 10 +++--- .../Message Provider/MessageService.swift | 2 +- .../Contacts Service/ContactsService.swift | 6 ++-- FlowCrypt/Models/Common/Recipient.swift | 7 +++- .../Key Services/Models/RecipientTests.swift | 34 ++++++++++++------- .../Services/Key Services/PubLookupTest.swift | 2 +- .../Mocks/ContactsServiceMock.swift | 2 +- .../SelectRecipientByName.spec.ts | 2 +- 8 files changed, 39 insertions(+), 26 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 76c6698ee..eebac014d 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -1068,7 +1068,7 @@ extension ComposeViewController { let cloudRecipients = try await service.searchContacts(query: query) let localRecipients = try contactsService.searchLocalContacts(query: query) - let recipients = (localRecipients + cloudRecipients) + let recipients = (cloudRecipients + localRecipients) .map(Recipient.init) .unique() .sorted() @@ -1095,8 +1095,8 @@ extension ComposeViewController { handleEvaluation(for: contact) } - let recipient = Recipient(recipient: recipient) - let contactWithFetchedKeys = try await service.fetchContact(recipient) + let contact = Recipient(recipient: recipient) + let contactWithFetchedKeys = try await service.fetch(contact: contact) handleEvaluation(for: contactWithFetchedKeys) } catch { handleEvaluation(error: error, with: recipient.email) @@ -1574,7 +1574,7 @@ private actor ServiceActor { return try await contactsService.findLocalContact(with: email) } - func fetchContact(_ contact: Recipient) async throws -> RecipientWithSortedPubKeys { - return try await contactsService.fetchContact(contact) + func fetch(contact: Recipient) async throws -> RecipientWithSortedPubKeys { + return try await contactsService.fetch(contact: contact) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index c03eae452..859938e9b 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -235,7 +235,7 @@ extension MessageService { let pubKeys = try contactsService.retrievePubKeys(for: sender.email) if pubKeys.isNotEmpty || onlyLocal { return pubKeys } - guard let contact = try? await contactsService.fetchContact(sender) + guard let contact = try? await contactsService.fetch(contact: sender) else { return [] } return contact.pubKeys.map(\.armored) diff --git a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift index ea11d67c7..8cae8b825 100644 --- a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift @@ -19,7 +19,7 @@ protocol ContactsServiceType: PublicKeyProvider, ContactsProviderType { protocol ContactsProviderType { func findLocalContact(with email: String) async throws -> RecipientWithSortedPubKeys? func searchLocalContacts(query: String) throws -> [RecipientBase] - func fetchContact(_ contact: Recipient) async throws -> RecipientWithSortedPubKeys + func fetch(contact: Recipient) async throws -> RecipientWithSortedPubKeys } protocol PublicKeyProvider { @@ -51,8 +51,8 @@ extension ContactsService: ContactsProviderType { try localContactsProvider.searchRecipients(query: query) } - func fetchContact(_ recipient: Recipient) async throws -> RecipientWithSortedPubKeys { - let lookupRecipient = try await pubLookup.lookup(recipient: recipient) + func fetch(contact: Recipient) async throws -> RecipientWithSortedPubKeys { + let lookupRecipient = try await pubLookup.lookup(recipient: contact) try localContactsProvider.updateKeys(for: lookupRecipient) return lookupRecipient } diff --git a/FlowCrypt/Models/Common/Recipient.swift b/FlowCrypt/Models/Common/Recipient.swift index d82d7449b..3788a70f5 100644 --- a/FlowCrypt/Models/Common/Recipient.swift +++ b/FlowCrypt/Models/Common/Recipient.swift @@ -54,8 +54,13 @@ extension Recipient { } init(recipient: RecipientBase) { - self.name = recipient.name self.email = recipient.email + self.name = recipient.name + } + + init(email: String, name: String? = nil) { + self.email = email + self.name = name } } diff --git a/FlowCryptAppTests/Functionality/Services/Key Services/Models/RecipientTests.swift b/FlowCryptAppTests/Functionality/Services/Key Services/Models/RecipientTests.swift index 4c47ecec6..cc4430d38 100644 --- a/FlowCryptAppTests/Functionality/Services/Key Services/Models/RecipientTests.swift +++ b/FlowCryptAppTests/Functionality/Services/Key Services/Models/RecipientTests.swift @@ -13,34 +13,31 @@ class RecipientTests: XCTestCase { private let calendar = Calendar.current func testRecipientWithRevokedKey() { - let keyDetails = EncryptedStorageMock.createFakeKeyDetails(expiration: nil, revoked: true) - let recipient = RecipientWithSortedPubKeys(email: "test@flowcrypt.com", keyDetails: [keyDetails]) - + let recipient = createRecipient(keyExpiration: nil, isKeyRevoked: true) XCTAssertEqual(recipient.keyState, .revoked) } func testRecipientWithExpiredKey() { let expiration = Date().timeIntervalSince1970 - 60 * 60 - let keyDetails = EncryptedStorageMock.createFakeKeyDetails(expiration: Int(expiration)) - - let recipient = RecipientWithSortedPubKeys(email: "test@flowcrypt.com", keyDetails: [keyDetails]) + let recipient = createRecipient(keyExpiration: Int(expiration)) XCTAssertEqual(recipient.keyState, .expired) } func testRecipientWithValidKey() { let expiration = Date().timeIntervalSince1970 + 60 * 60 - let keyDetails = EncryptedStorageMock.createFakeKeyDetails(expiration: Int(expiration)) - let recipient = RecipientWithSortedPubKeys(email: "test@flowcrypt.com", keyDetails: [keyDetails]) + let recipient = createRecipient(keyExpiration: Int(expiration)) XCTAssertEqual(recipient.keyState, .active) XCTAssertEqual(recipient.pubKeys.first?.emails, ["test@flowcrypt.com"]) - let keyDetails2 = EncryptedStorageMock.createFakeKeyDetails(expiration: nil) - let recipient2 = RecipientWithSortedPubKeys(email: "test@flowcrypt.com", keyDetails: [keyDetails2]) + let recipient2 = createRecipient(keyExpiration: nil) XCTAssertEqual(recipient2.keyState, .active) } func testRecipientWithoutPubKey() { - let recipient = RecipientWithSortedPubKeys(email: "test@flowcrypt.com", keyDetails: []) + let recipient = RecipientWithSortedPubKeys( + Recipient(email: "test@flowcrypt.com"), + keyDetails: [] + ) XCTAssertEqual(recipient.keyState, .empty) } @@ -57,8 +54,10 @@ class RecipientTests: XCTestCase { let oldExpiredKey = EncryptedStorageMock.createFakeKeyDetails(expiration: now - 2 * 3600) let keyDetails = [revokedKey, oldExpiredKey, activeKey1, expiredKey, activeKey2, nonExpiringKey, activeKey3] - let recipient = RecipientWithSortedPubKeys(email: "test@flowcrypt.com", - keyDetails: keyDetails) + let recipient = RecipientWithSortedPubKeys( + Recipient(email: "test@flowcrypt.com"), + keyDetails: keyDetails + ) XCTAssertEqual(recipient.pubKeys[0].fingerprint, nonExpiringKey.primaryFingerprint) XCTAssertEqual(recipient.pubKeys[1].fingerprint, activeKey3.primaryFingerprint) @@ -68,4 +67,13 @@ class RecipientTests: XCTestCase { XCTAssertEqual(recipient.pubKeys[5].fingerprint, oldExpiredKey.primaryFingerprint) XCTAssertEqual(recipient.pubKeys[6].fingerprint, revokedKey.primaryFingerprint) } + + private func createRecipient(keyExpiration: Int?, isKeyRevoked: Bool = false) -> RecipientWithSortedPubKeys { + let keyDetails = EncryptedStorageMock.createFakeKeyDetails( + expiration: keyExpiration, + revoked: isKeyRevoked + ) + let recipient = Recipient(email: "test@flowcrypt.com") + return RecipientWithSortedPubKeys(recipient: recipient, keyDetails: [keyDetails]) + } } diff --git a/FlowCryptAppTests/Functionality/Services/Key Services/PubLookupTest.swift b/FlowCryptAppTests/Functionality/Services/Key Services/PubLookupTest.swift index 55375cec0..50b6c37b9 100644 --- a/FlowCryptAppTests/Functionality/Services/Key Services/PubLookupTest.swift +++ b/FlowCryptAppTests/Functionality/Services/Key Services/PubLookupTest.swift @@ -20,7 +20,7 @@ class PubLookupTest: XCTestCase { raw: RawClientConfiguration() ) ) - let r = try await pubLookup.lookup(email: "different.uid@recipient.test") + let r = try await pubLookup.lookup(recipient: Recipient(email: "different.uid@recipient.test")) XCTAssertTrue(r.pubKeys.isNotEmpty, "expected pubkeys not empty") XCTAssertEqual(r.pubKeys.first?.longid, "0C9C2E6A4D273C6F") } diff --git a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift index fe653169a..f1d4addf9 100644 --- a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift +++ b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift @@ -16,7 +16,7 @@ final class ContactsServiceMock: ContactsServiceType { } var fetchContactResult: Result! - func fetchContact(with email: String) async throws -> RecipientWithSortedPubKeys { + func fetch(contact: Recipient) async throws -> RecipientWithSortedPubKeys { switch fetchContactResult { case .success(let result): return result diff --git a/appium/tests/specs/live/composeEmail/SelectRecipientByName.spec.ts b/appium/tests/specs/live/composeEmail/SelectRecipientByName.spec.ts index e21d93a71..a2b428f45 100644 --- a/appium/tests/specs/live/composeEmail/SelectRecipientByName.spec.ts +++ b/appium/tests/specs/live/composeEmail/SelectRecipientByName.spec.ts @@ -18,7 +18,7 @@ describe('COMPOSE EMAIL: ', () => { const firstContactEmail = CommonData.contact.email; const firstContactName = CommonData.contact.name; - const firstContactItemName = 'Dmitry at FlowCrypt'; + const firstContactItemName = 'Dima Flowcrypt'; const secondContactEmail = CommonData.secondContact.email; const secondContactName = CommonData.secondContact.name; From 04e5b7173ad2cd37f0b6efe2a125b86796ec36e3 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 23 Feb 2022 14:19:37 +0200 Subject: [PATCH 07/22] update public key detail screen --- .../Compose/ComposeViewController.swift | 2 +- .../Contacts List/ContactDetailDecorator.swift | 2 +- .../Models/RecipientWithSortedPubKeys.swift | 2 +- appium/tests/data/index.ts | 2 +- .../screenobjects/contact-public-key.screen.ts | 5 +++-- .../composeEmail/SelectRecipientByName.spec.ts | 14 ++++++-------- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index eebac014d..1a8007d5e 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -33,7 +33,7 @@ final class ComposeViewController: TableNodeViewController { } private enum Constants { - static let endTypingCharacters = [",", " ", "\n", ";"] + static let endTypingCharacters = [",", "\n", ";"] static let minRecipientsPartHeight: CGFloat = 44 } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift index 333c71d0b..e495c0d66 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift @@ -14,7 +14,7 @@ struct ContactDetailDecorator { func userNodeInput(with contact: RecipientWithSortedPubKeys) -> ContactUserCellNode.Input { ContactUserCellNode.Input( - user: (contact.name ?? contact.email).attributed(.regular(16)) + user: contact.formatted.attributed(.regular(16)) ) } diff --git a/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift b/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift index 5e2a041c1..2fbf17868 100644 --- a/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift +++ b/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift @@ -8,7 +8,7 @@ import Foundation -struct RecipientWithSortedPubKeys { +struct RecipientWithSortedPubKeys: RecipientBase { let email: String /// name if known let name: String? diff --git a/appium/tests/data/index.ts b/appium/tests/data/index.ts index faec23766..dc54c2cfa 100644 --- a/appium/tests/data/index.ts +++ b/appium/tests/data/index.ts @@ -25,7 +25,7 @@ export const CommonData = { }, contact: { email: 'dmitry@flowcrypt.com', - name: 'Dima' + name: 'Dima Flowcrypt' }, secondContact: { email: 'demo@flowcrypt.com', diff --git a/appium/tests/screenobjects/contact-public-key.screen.ts b/appium/tests/screenobjects/contact-public-key.screen.ts index 5796deafe..fa1b73856 100644 --- a/appium/tests/screenobjects/contact-public-key.screen.ts +++ b/appium/tests/screenobjects/contact-public-key.screen.ts @@ -77,10 +77,11 @@ class ContactPublicKeyScreen extends BaseScreen { await ElementHelper.waitElementInvisible(await this.expiresValue); } - checkPgpUserId = async (email: string) => { + checkPgpUserId = async (email: string, name?: string) => { + const value = name ? `${name} <${email}>` : email await (await this.trashButton).waitForDisplayed(); await (await this.pgpUserIdLabel).waitForDisplayed(); - expect(await (await this.pgpUserIdEmailValue).getAttribute('value')).toContain(email); + expect(await (await this.pgpUserIdEmailValue).getAttribute('value')).toContain(value); } clickOnFingerPrint = async () => { diff --git a/appium/tests/specs/live/composeEmail/SelectRecipientByName.spec.ts b/appium/tests/specs/live/composeEmail/SelectRecipientByName.spec.ts index a2b428f45..a5d9a947c 100644 --- a/appium/tests/specs/live/composeEmail/SelectRecipientByName.spec.ts +++ b/appium/tests/specs/live/composeEmail/SelectRecipientByName.spec.ts @@ -18,11 +18,9 @@ describe('COMPOSE EMAIL: ', () => { const firstContactEmail = CommonData.contact.email; const firstContactName = CommonData.contact.name; - const firstContactItemName = 'Dima Flowcrypt'; const secondContactEmail = CommonData.secondContact.email; const secondContactName = CommonData.secondContact.name; - const secondContactItemName = 'Demo key 2'; await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); @@ -47,13 +45,13 @@ describe('COMPOSE EMAIL: ', () => { // Add first contact await MailFolderScreen.clickCreateEmail(); await NewMessageScreen.setAddRecipientByName(firstContactName, firstContactEmail); - await NewMessageScreen.checkAddedRecipient(firstContactEmail); + await NewMessageScreen.checkAddedRecipient(firstContactName); await NewMessageScreen.clickBackButton(); // Add second contact await MailFolderScreen.clickCreateEmail(); await NewMessageScreen.setAddRecipientByName(secondContactName, secondContactEmail); - await NewMessageScreen.checkAddedRecipient(secondContactEmail); + await NewMessageScreen.checkAddedRecipient(secondContactName); await NewMessageScreen.clickBackButton(); // Go to Contacts screen @@ -65,13 +63,13 @@ describe('COMPOSE EMAIL: ', () => { await SettingsScreen.clickOnSettingItem('Contacts'); await ContactScreen.checkContactScreen(); - await ContactScreen.checkContact(firstContactItemName); - await ContactScreen.checkContact(secondContactItemName); + await ContactScreen.checkContact(firstContactName); + await ContactScreen.checkContact(secondContactName); // Go to Contact screen - await ContactScreen.clickOnContact(firstContactItemName); + await ContactScreen.clickOnContact(firstContactName); - await ContactPublicKeyScreen.checkPgpUserId(firstContactEmail); + await ContactPublicKeyScreen.checkPgpUserId(firstContactEmail, firstContactName); await ContactPublicKeyScreen.checkPublicKeyDetailsNotEmpty(); await ContactPublicKeyScreen.clickOnFingerPrint(); await PublicKeyDetailsScreen.checkPublicKeyDetailsScreen(); From b3bd61f09d3d182cca09932d6381474eeb6d3346 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 23 Feb 2022 16:36:27 +0200 Subject: [PATCH 08/22] code cleanup --- .../Compose/ComposeViewController.swift | 30 ++++++++++++------- .../CloudContactsProvider.swift | 17 ++++++----- .../Message Provider/MessageService.swift | 2 +- .../ComposeMessageService.swift | 8 +++-- .../Contacts Service/ContactsService.swift | 10 +++---- .../Models/RecipientWithSortedPubKeys.swift | 11 +------ .../LocalContactsProvider.swift | 12 ++++---- .../Remote Pub Key Services/PubLookup.swift | 4 +-- .../Realm Models/RecipientRealmObject.swift | 2 +- .../Key Services/Models/RecipientTests.swift | 2 +- .../Mocks/ContactsServiceMock.swift | 4 +-- .../contact-public-key.screen.ts | 2 +- 12 files changed, 54 insertions(+), 50 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 1a8007d5e..cfb92c246 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -643,9 +643,14 @@ extension ComposeViewController: ASTableDelegate, ASTableDataSource { guard let recipient = recipients[safe: indexPath.row-1] else { return ASCellNode() } if let name = recipient.name { - return LabelCellNode(input: self.decorator.styledRecipientInfo(with: recipient.email, name: name)) + let input = self.decorator.styledRecipientInfo( + with: recipient.email, + name: name + ) + return LabelCellNode(input: input) } else { - return InfoCellNode(input: self.decorator.styledRecipientInfo(with: recipient.email)) + let input = self.decorator.styledRecipientInfo(with: recipient.email) + return InfoCellNode(input: input) } case (.searchEmails, .contacts): return indexPath.row == 0 ? DividerCellNode() : self.enableGoogleContactsNode() @@ -961,7 +966,13 @@ extension ComposeViewController { contextToSend.set(recipients: idleRecipients, for: recipientType) - let newRecipient = ComposeMessageRecipient(email: email, name: name, type: recipientType, state: decorator.recipientIdleState) + let newRecipient = ComposeMessageRecipient( + email: email, + name: name, + type: recipientType, + state: decorator.recipientIdleState + ) + let indexOfRecipient: Int let indexPath = recipientsIndexPath(type: recipientType, part: .list) @@ -1069,9 +1080,8 @@ extension ComposeViewController { let localRecipients = try contactsService.searchLocalContacts(query: query) let recipients = (cloudRecipients + localRecipients) - .map(Recipient.init) - .unique() - .sorted() + .unique() + .sorted() updateState(with: .searchEmails(recipients)) } catch { @@ -1096,7 +1106,7 @@ extension ComposeViewController { } let contact = Recipient(recipient: recipient) - let contactWithFetchedKeys = try await service.fetch(contact: contact) + let contactWithFetchedKeys = try await service.fetchPubKeys(for: contact) handleEvaluation(for: contactWithFetchedKeys) } catch { handleEvaluation(error: error, with: recipient.email) @@ -1566,7 +1576,7 @@ private actor ServiceActor { ) } - func searchContacts(query: String) async throws -> [RecipientBase] { + func searchContacts(query: String) async throws -> [Recipient] { return try await cloudContactProvider.searchContacts(query: query) } @@ -1574,7 +1584,7 @@ private actor ServiceActor { return try await contactsService.findLocalContact(with: email) } - func fetch(contact: Recipient) async throws -> RecipientWithSortedPubKeys { - return try await contactsService.fetch(contact: contact) + func fetchPubKeys(for recipient: Recipient) async throws -> RecipientWithSortedPubKeys { + return try await contactsService.fetchPubKeys(for: recipient) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift index 2c1e187d9..4c0d0159f 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift @@ -11,7 +11,7 @@ import GoogleAPIClientForREST_PeopleService protocol CloudContactsProvider { var isContactsScopeEnabled: Bool { get } - func searchContacts(query: String) async throws -> [RecipientBase] + func searchContacts(query: String) async throws -> [Recipient] } enum CloudContactsProviderError: Error { @@ -81,26 +81,27 @@ final class UserContactsProvider { } extension UserContactsProvider: CloudContactsProvider { - func searchContacts(query: String) async -> [RecipientBase] { + func searchContacts(query: String) async -> [Recipient] { guard isContactsScopeEnabled else { return [] } let contacts = await searchUserContacts(query: query, type: .contacts) let otherContacts = await searchUserContacts(query: query, type: .other) - return contacts + otherContacts - // TODO - // let emails = Set(contacts + otherContacts) - // return Array(emails).sorted(by: >) + let allRecipients = (contacts + otherContacts) + .map(Recipient.init) + .unique() + .sorted() + return allRecipients } } extension UserContactsProvider { - private func searchUserContacts(query: String, type: QueryType) async -> [RecipientBase] { + private func searchUserContacts(query: String, type: QueryType) async -> [Recipient] { let query = type.query(searchString: query) guard let emails = try? await perform(query: query) else { return [] } return emails } - private func perform(query: GTLRPeopleServiceQuery) async throws -> [RecipientBase] { + private func perform(query: GTLRPeopleServiceQuery) async throws -> [Recipient] { try await withCheckedThrowingContinuation { continuation in self.peopleService.executeQuery(query) { _, data, error in if let error = error { diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 859938e9b..274888940 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -235,7 +235,7 @@ extension MessageService { let pubKeys = try contactsService.retrievePubKeys(for: sender.email) if pubKeys.isNotEmpty || onlyLocal { return pubKeys } - guard let contact = try? await contactsService.fetch(contact: sender) + guard let contact = try? await contactsService.fetchPubKeys(for: sender) else { return [] } return contact.pubKeys.map(\.armored) diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index c46abdb10..f2ae28cf0 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -147,13 +147,15 @@ final class ComposeMessageService { ) } - private func getRecipientKeys(for recipients: [ComposeMessageRecipient]) async throws -> [RecipientWithSortedPubKeys] { + private func getRecipientKeys(for composeRecipients: [ComposeMessageRecipient]) async throws -> [RecipientWithSortedPubKeys] { + let recipients = composeRecipients.map(Recipient.init) var recipientsWithKeys: [RecipientWithSortedPubKeys] = [] for recipient in recipients { let armoredPubkeys = try contactsService.retrievePubKeys(for: recipient.email).joined(separator: "\n") let parsed = try await self.core.parseKeys(armoredOrBinary: armoredPubkeys.data()) - let recipient = Recipient(recipient: recipient) // TODO: - recipientsWithKeys.append(RecipientWithSortedPubKeys(recipient: recipient, keyDetails: parsed.keyDetails)) + recipientsWithKeys.append( + RecipientWithSortedPubKeys(recipient, keyDetails: parsed.keyDetails) + ) } return recipientsWithKeys } diff --git a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift index 8cae8b825..8c308645d 100644 --- a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift @@ -18,8 +18,8 @@ protocol ContactsServiceType: PublicKeyProvider, ContactsProviderType { protocol ContactsProviderType { func findLocalContact(with email: String) async throws -> RecipientWithSortedPubKeys? - func searchLocalContacts(query: String) throws -> [RecipientBase] - func fetch(contact: Recipient) async throws -> RecipientWithSortedPubKeys + func searchLocalContacts(query: String) throws -> [Recipient] + func fetchPubKeys(for recipient: Recipient) async throws -> RecipientWithSortedPubKeys } protocol PublicKeyProvider { @@ -47,12 +47,12 @@ extension ContactsService: ContactsProviderType { return try await localContactsProvider.searchRecipient(with: email) } - func searchLocalContacts(query: String) throws -> [RecipientBase] { + func searchLocalContacts(query: String) throws -> [Recipient] { try localContactsProvider.searchRecipients(query: query) } - func fetch(contact: Recipient) async throws -> RecipientWithSortedPubKeys { - let lookupRecipient = try await pubLookup.lookup(recipient: contact) + func fetchPubKeys(for recipient: Recipient) async throws -> RecipientWithSortedPubKeys { + let lookupRecipient = try await pubLookup.lookup(recipient: recipient) try localContactsProvider.updateKeys(for: lookupRecipient) return lookupRecipient } diff --git a/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift b/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift index 2fbf17868..cb751a3c7 100644 --- a/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift +++ b/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift @@ -25,18 +25,9 @@ struct RecipientWithSortedPubKeys: RecipientBase { extension RecipientWithSortedPubKeys { init(_ recipient: Recipient, keyDetails: [KeyDetails] = []) { - self.email = recipient.email - self.name = recipient.name - self.lastUsed = recipient.lastUsed - self._pubKeys = keyDetails.map(PubKey.init) - } -} - -extension RecipientWithSortedPubKeys { - init(recipient: Recipient, keyDetails: [KeyDetails]) { self.email = recipient.email self.name = recipient.name ?? keyDetails.first?.users.first ?? email - self.lastUsed = nil + self.lastUsed = recipient.lastUsed self._pubKeys = keyDetails.map(PubKey.init) } } diff --git a/FlowCrypt/Functionality/Services/Local Contacts Service/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Local Contacts Service/LocalContactsProvider.swift index caa91e17f..2252814d6 100644 --- a/FlowCrypt/Functionality/Services/Local Contacts Service/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Local Contacts Service/LocalContactsProvider.swift @@ -12,7 +12,7 @@ import FlowCryptCommon protocol LocalContactsProviderType: PublicKeyProvider { func searchRecipient(with email: String) async throws -> RecipientWithSortedPubKeys? - func searchRecipients(query: String) throws -> [RecipientBase] + func searchRecipients(query: String) throws -> [Recipient] func save(recipient: RecipientWithSortedPubKeys) throws func remove(recipient: RecipientWithSortedPubKeys) throws func updateKeys(for recipient: RecipientWithSortedPubKeys) throws @@ -91,11 +91,11 @@ extension LocalContactsProvider: LocalContactsProviderType { return try await parseRecipient(from: recipient) } - func searchRecipients(query: String) throws -> [RecipientBase] { - let recipients = try storage - .objects(RecipientRealmObject.self) - .filter("email contains[c] %@", query) - return Array(recipients) + func searchRecipients(query: String) throws -> [Recipient] { + try storage + .objects(RecipientRealmObject.self) + .filter("email contains[c] %@", query) + .map(Recipient.init) } func getAllRecipients() async throws -> [RecipientWithSortedPubKeys] { diff --git a/FlowCrypt/Functionality/Services/Remote Pub Key Services/PubLookup.swift b/FlowCrypt/Functionality/Services/Remote Pub Key Services/PubLookup.swift index 52f6619d5..814cb8830 100644 --- a/FlowCrypt/Functionality/Services/Remote Pub Key Services/PubLookup.swift +++ b/FlowCrypt/Functionality/Services/Remote Pub Key Services/PubLookup.swift @@ -56,10 +56,10 @@ class PubLookup: PubLookupType { if !wkdResult.keys.isEmpty { // WKD keys are preferred. The trust level is higher because the recipient // controls the distribution of the keys themselves on their own domain - return RecipientWithSortedPubKeys(recipient: recipient, keyDetails: wkdResult.keys) + return RecipientWithSortedPubKeys(recipient, keyDetails: wkdResult.keys) } // Attester keys are less preferred because they come from less trustworthy source // (the FlowCrypt server) - return RecipientWithSortedPubKeys(recipient: recipient, keyDetails: attesterResult.keys) + return RecipientWithSortedPubKeys(recipient, keyDetails: attesterResult.keys) } } diff --git a/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift b/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift index 7bb7a5bb2..99bca1721 100644 --- a/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift @@ -9,7 +9,7 @@ import Foundation import RealmSwift -final class RecipientRealmObject: Object, RecipientBase { +final class RecipientRealmObject: Object { @Persisted(primaryKey: true) var email: String @Persisted var name: String? @Persisted var lastUsed: Date? diff --git a/FlowCryptAppTests/Functionality/Services/Key Services/Models/RecipientTests.swift b/FlowCryptAppTests/Functionality/Services/Key Services/Models/RecipientTests.swift index cc4430d38..7a11b142b 100644 --- a/FlowCryptAppTests/Functionality/Services/Key Services/Models/RecipientTests.swift +++ b/FlowCryptAppTests/Functionality/Services/Key Services/Models/RecipientTests.swift @@ -74,6 +74,6 @@ class RecipientTests: XCTestCase { revoked: isKeyRevoked ) let recipient = Recipient(email: "test@flowcrypt.com") - return RecipientWithSortedPubKeys(recipient: recipient, keyDetails: [keyDetails]) + return RecipientWithSortedPubKeys(recipient, keyDetails: [keyDetails]) } } diff --git a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift index f1d4addf9..ee3c70d37 100644 --- a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift +++ b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift @@ -16,7 +16,7 @@ final class ContactsServiceMock: ContactsServiceType { } var fetchContactResult: Result! - func fetch(contact: Recipient) async throws -> RecipientWithSortedPubKeys { + func fetchPubKeys(for recipient: Recipient) async throws -> RecipientWithSortedPubKeys { switch fetchContactResult { case .success(let result): return result @@ -39,7 +39,7 @@ final class ContactsServiceMock: ContactsServiceType { } } - func searchLocalContacts(query: String) throws -> [RecipientBase] { [] } + func searchLocalContacts(query: String) throws -> [Recipient] { [] } func removePubKey(with fingerprint: String, for email: String) {} } diff --git a/appium/tests/screenobjects/contact-public-key.screen.ts b/appium/tests/screenobjects/contact-public-key.screen.ts index fa1b73856..36caba2a6 100644 --- a/appium/tests/screenobjects/contact-public-key.screen.ts +++ b/appium/tests/screenobjects/contact-public-key.screen.ts @@ -78,7 +78,7 @@ class ContactPublicKeyScreen extends BaseScreen { } checkPgpUserId = async (email: string, name?: string) => { - const value = name ? `${name} <${email}>` : email + const value = name ? `${name} <${email}>` : email; await (await this.trashButton).waitForDisplayed(); await (await this.pgpUserIdLabel).waitForDisplayed(); expect(await (await this.pgpUserIdEmailValue).getAttribute('value')).toContain(value); From ac0b0b9f058d9743bfff4304369817e60520a3d7 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 23 Feb 2022 23:12:52 +0200 Subject: [PATCH 09/22] fixes for ui tests --- .../Controllers/Threads/ThreadDetailsDecorator.swift | 2 +- appium/tests/data/index.ts | 4 ++++ appium/tests/helpers/PublicKeyHelper.ts | 2 +- appium/tests/screenobjects/email.screen.ts | 6 +++--- .../CheckRecipientColorAfterRemovingPubKey.spec.ts | 2 +- .../inbox/CheckEncryptedEmailAfterRestartApp.spec.ts | 8 ++++---- .../live/inbox/CheckMessageProcessingErrors.spec.ts | 12 ++++++------ 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index b54339592..2b1a07c22 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -11,7 +11,7 @@ import UIKit extension ThreadMessageInfoCellNode.Input { init(threadMessage: ThreadDetailsViewController.Input) { - let sender = threadMessage.rawMessage.sender?.shortName ?? "message_unknown_sender".localized + let sender = threadMessage.rawMessage.sender?.displayName ?? "message_unknown_sender".localized let recipientPrefix = "to".localized let recipientsList = threadMessage.rawMessage .allRecipients diff --git a/appium/tests/data/index.ts b/appium/tests/data/index.ts index dc54c2cfa..2a939135a 100644 --- a/appium/tests/data/index.ts +++ b/appium/tests/data/index.ts @@ -22,6 +22,7 @@ export const CommonData = { }, sender: { email: 'dmitry@flowcrypt.com', + name: 'Dmitry at FlowCrypt' }, contact: { email: 'dmitry@flowcrypt.com', @@ -91,11 +92,13 @@ export const CommonData = { subject: 'message encrypted for another public key (only one pubkey used)', message: 'key_mismatch: Missing appropriate key', senderEmail: 'flowcrypt.compatibility@gmail.com', + senderName: 'FlowCrypt Compatibility' }, wrongChecksumEmail: { subject: 'wrong checksum', message: 'format: Error: Ascii armor integrity check on message failed: \'FdCC\' should be \'FddK\'', senderEmail: 'flowcrypt.compatibility@gmail.com', + senderName: 'FlowCrypt Compatibility' }, notIntegrityProtected: { subject: 'not integrity protected - should show a warning and not decrypt automatically', @@ -106,6 +109,7 @@ export const CommonData = { subject: 'key mismatch unexpectedly produces a modal', message: 'Here are the images for testing compatibility.', senderEmail: 'sunitnandi834@gmail.com', + senderName: 'Sunit Kumar Nandi', encryptedBadgeText: 'encrypted', signatureBadgeText: 'not signed', firstAttachmentName: 'Screenshot_20180422_125217.png.asc', diff --git a/appium/tests/helpers/PublicKeyHelper.ts b/appium/tests/helpers/PublicKeyHelper.ts index 94cb6f879..b06f07ca1 100644 --- a/appium/tests/helpers/PublicKeyHelper.ts +++ b/appium/tests/helpers/PublicKeyHelper.ts @@ -39,7 +39,7 @@ class PublicKeyHelper { // Add first contact await MailFolderScreen.clickCreateEmail(); await NewMessageScreen.setAddRecipientByName(userName, userEmail); - await NewMessageScreen.checkAddedRecipientColor(userEmail, 0, 'green'); + await NewMessageScreen.checkAddedRecipientColor(userName, 0, 'green'); await NewMessageScreen.clickBackButton(); // Go to Contacts screen diff --git a/appium/tests/screenobjects/email.screen.ts b/appium/tests/screenobjects/email.screen.ts index c96e664c9..115d1eec7 100644 --- a/appium/tests/screenobjects/email.screen.ts +++ b/appium/tests/screenobjects/email.screen.ts @@ -121,8 +121,8 @@ class EmailScreen extends BaseScreen { return $(SELECTORS.ATTACHMENT_TEXT_VIEW); } - checkEmailAddress = async (email: string) => { - await ElementHelper.checkStaticText(await this.senderEmail, email); + checkEmailSender = async (sender: string) => { + await ElementHelper.checkStaticText(await this.senderEmail, sender); } checkEmailSubject = async (subject: string) => { @@ -136,7 +136,7 @@ class EmailScreen extends BaseScreen { } checkOpenedEmail = async (email: string, subject: string, text: string) => { - await this.checkEmailAddress(email); + await this.checkEmailSender(email); await this.checkEmailSubject(subject); await this.checkEmailText(text); } diff --git a/appium/tests/specs/live/composeEmail/CheckRecipientColorAfterRemovingPubKey.spec.ts b/appium/tests/specs/live/composeEmail/CheckRecipientColorAfterRemovingPubKey.spec.ts index da9e94f29..49f964913 100644 --- a/appium/tests/specs/live/composeEmail/CheckRecipientColorAfterRemovingPubKey.spec.ts +++ b/appium/tests/specs/live/composeEmail/CheckRecipientColorAfterRemovingPubKey.spec.ts @@ -26,7 +26,7 @@ describe('COMPOSE EMAIL: ', () => { await PublicKeyHelper.addRecipientAndCheckFetchedKey(contactName, contactEmail); await PublicKeyDetailsScreen.clickTrashButton(); - await ContactPublicKeyScreen.checkPgpUserId(contactEmail); + await ContactPublicKeyScreen.checkPgpUserId(contactEmail, contactName); await ContactPublicKeyScreen.checkPublicKeyDetailsNotDisplayed(); await ContactPublicKeyScreen.clickBackButton(); diff --git a/appium/tests/specs/live/inbox/CheckEncryptedEmailAfterRestartApp.spec.ts b/appium/tests/specs/live/inbox/CheckEncryptedEmailAfterRestartApp.spec.ts index 396abd5c4..b8ee2c16c 100644 --- a/appium/tests/specs/live/inbox/CheckEncryptedEmailAfterRestartApp.spec.ts +++ b/appium/tests/specs/live/inbox/CheckEncryptedEmailAfterRestartApp.spec.ts @@ -12,7 +12,7 @@ describe('INBOX: ', () => { it('user is able to see encrypted email with pass phrase after restart app', async () => { - const senderEmail = CommonData.sender.email; + const senderName = CommonData.sender.name; const emailSubject = CommonData.encryptedEmail.subject; const emailText = CommonData.encryptedEmail.message; const wrongPassPhrase = 'wrong'; @@ -27,7 +27,7 @@ describe('INBOX: ', () => { await MailFolderScreen.clickSearchButton(); await SearchScreen.searchAndClickEmailBySubject(emailSubject); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); await driver.terminateApp(bundleId); await driver.activateApp(bundleId); @@ -44,11 +44,11 @@ describe('INBOX: ', () => { //check email after setting correct pass phrase await EmailScreen.enterPassPhrase(correctPassPhrase); await EmailScreen.clickOkButton(); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); //reopen email without pass phrase await EmailScreen.clickBackButton(); await MailFolderScreen.clickOnEmailBySubject(emailSubject); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); }); }); diff --git a/appium/tests/specs/live/inbox/CheckMessageProcessingErrors.spec.ts b/appium/tests/specs/live/inbox/CheckMessageProcessingErrors.spec.ts index 842871ae6..5b8daad70 100644 --- a/appium/tests/specs/live/inbox/CheckMessageProcessingErrors.spec.ts +++ b/appium/tests/specs/live/inbox/CheckMessageProcessingErrors.spec.ts @@ -22,12 +22,12 @@ describe('INBOX: ', () => { // Const for message encrypted for another public key const encryptedForAnotherPublicKeySubject = CommonData.encryptedForAnotherPublicKeyEmail.subject; - const encryptedForAnotherPublicKeyEmail = CommonData.encryptedForAnotherPublicKeyEmail.senderEmail; + const encryptedForAnotherPublicKeyName = CommonData.encryptedForAnotherPublicKeyEmail.senderName; const encryptedForAnotherPublicKeyText = CommonData.encryptedForAnotherPublicKeyEmail.message; // Const for encrypted for a wrong checksum message const wrongChecksumSubject = CommonData.wrongChecksumEmail.subject; - const wrongChecksumEmail = CommonData.wrongChecksumEmail.senderEmail; + const wrongChecksumName = CommonData.wrongChecksumEmail.senderName; const wrongChecksumText = CommonData.wrongChecksumEmail.message; const notIntegrityProtectedSubject = CommonData.notIntegrityProtected.subject; @@ -35,7 +35,7 @@ describe('INBOX: ', () => { const notIntegrityProtectedText = CommonData.notIntegrityProtected.message; const keyMismatchSubject = CommonData.keyMismatch.subject; - const keyMismatchEmail = CommonData.keyMismatch.senderEmail; + const keyMismatchName = CommonData.keyMismatch.senderName; const keyMismatchText = CommonData.keyMismatch.message; const keyMismatchEncryptedBadge = CommonData.keyMismatch.encryptedBadgeText; const keyMismatchSignatureBadge= CommonData.keyMismatch.signatureBadgeText; @@ -60,7 +60,7 @@ describe('INBOX: ', () => { // Checking error message encrypted for another public key await MailFolderScreen.clickSearchButton(); await SearchScreen.searchAndClickEmailBySubject(encryptedForAnotherPublicKeySubject); - await EmailScreen.checkOpenedEmail(encryptedForAnotherPublicKeyEmail, encryptedForAnotherPublicKeySubject, encryptedForAnotherPublicKeyText); + await EmailScreen.checkOpenedEmail(encryptedForAnotherPublicKeyName, encryptedForAnotherPublicKeySubject, encryptedForAnotherPublicKeyText); await EmailScreen.checkEncryptionBadge(decryptErrorBadgeText); await EmailScreen.clickBackButton(); @@ -70,7 +70,7 @@ describe('INBOX: ', () => { // Checking error for wrong checksum message await MailFolderScreen.clickSearchButton(); await SearchScreen.searchAndClickEmailBySubject(wrongChecksumSubject); - await EmailScreen.checkOpenedEmail(wrongChecksumEmail, wrongChecksumSubject, wrongChecksumText); + await EmailScreen.checkOpenedEmail(wrongChecksumName, wrongChecksumSubject, wrongChecksumText); await EmailScreen.checkEncryptionBadge(decryptErrorBadgeText); // Checking error for integrity protected message @@ -92,7 +92,7 @@ describe('INBOX: ', () => { await SearchScreen.searchAndClickEmailBySubject(keyMismatchSubject); await MailFolderScreen.clickOnEmailBySubject(keyMismatchSubject); - await EmailScreen.checkOpenedEmail(keyMismatchEmail, keyMismatchSubject, keyMismatchText); + await EmailScreen.checkOpenedEmail(keyMismatchName, keyMismatchSubject, keyMismatchText); await EmailScreen.checkEncryptionBadge(keyMismatchEncryptedBadge); await EmailScreen.checkSignatureBadge(keyMismatchSignatureBadge); await EmailScreen.checkAttachment(firstAttachmentName); From fddf24bb7e6a4b8e4a2faa4d4e9912b9a5052c1c Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 28 Feb 2022 14:07:09 +0200 Subject: [PATCH 10/22] fix ui tests --- Gemfile.lock | 20 +++++++++---------- Podfile.lock | 4 ++-- appium/tests/data/index.ts | 2 ++ ...ckReplyAndForwardForEncryptedEmail.spec.ts | 7 ++++--- .../live/inbox/ReadAttachmentEmail.spec.ts | 10 +++++----- .../inbox/ReadEmailAfterRestartApp.spec.ts | 6 +++--- .../specs/live/inbox/ReadTextEmail.spec.ts | 4 ++-- .../CheckAppAfterUpdateFromOldVersion.spec.ts | 3 ++- 8 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 82db779d1..611c040a0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,17 +17,17 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.556.0) - aws-sdk-core (3.126.2) + aws-partitions (1.559.0) + aws-sdk-core (3.127.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.54.0) - aws-sdk-core (~> 3, >= 3.126.0) + aws-sdk-kms (1.55.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.112.0) - aws-sdk-core (~> 3, >= 3.126.0) + aws-sdk-s3 (1.113.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.4.0) @@ -191,8 +191,8 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.1.1) - faraday (>= 0.17.3, < 2.0) + googleauth (1.1.2) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) @@ -243,9 +243,9 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.16.0) + signet (0.16.1) addressable (~> 2.8) - faraday (>= 0.17.3, < 2.0) + faraday (>= 0.17.5, < 3.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) diff --git a/Podfile.lock b/Podfile.lock index d9907e738..039a2bd8e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,7 +15,7 @@ PODS: - PINRemoteImage/PINCache (3.0.3): - PINCache (~> 3.0.3) - PINRemoteImage/Core - - SwiftLint (0.46.2) + - SwiftLint (0.46.3) - SwiftyRSA (1.7.0): - SwiftyRSA/ObjC (= 1.7.0) - SwiftyRSA/ObjC (1.7.0) @@ -61,7 +61,7 @@ SPEC CHECKSUMS: PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01 - SwiftLint: 6bc52a21f0fd44cab9aa2dc8e534fb9f5e3ec507 + SwiftLint: ae76d82f056734f853c8cd0371503252c606c482 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3 diff --git a/appium/tests/data/index.ts b/appium/tests/data/index.ts index 2a939135a..80b3080c2 100644 --- a/appium/tests/data/index.ts +++ b/appium/tests/data/index.ts @@ -50,6 +50,7 @@ export const CommonData = { }, emailWithMultipleRecipients: { sender: 'flowcrypt.compatibility@gmail.com', + senderName: 'FlowCrypt Compatibility', recipient: 'robot@flowcrypt.com', subject: 'Message with multiple recipients and attachment', message: 'This email has multiple recipients and attachment', @@ -76,6 +77,7 @@ export const CommonData = { }, recipientsListEmail: { sender: 'flowcrypt.compatibility@gmail.com', + senderName: 'FlowCrypt Compatibility', subject: 'CC and BCC test', message: 'Test message for CC and BCC recipients', recipients: 'to Robot, robot+cc, e2e.enterprise.test', diff --git a/appium/tests/specs/live/inbox/CheckReplyAndForwardForEncryptedEmail.spec.ts b/appium/tests/specs/live/inbox/CheckReplyAndForwardForEncryptedEmail.spec.ts index 7ce1bdaac..964629e1e 100644 --- a/appium/tests/specs/live/inbox/CheckReplyAndForwardForEncryptedEmail.spec.ts +++ b/appium/tests/specs/live/inbox/CheckReplyAndForwardForEncryptedEmail.spec.ts @@ -14,6 +14,7 @@ describe('INBOX: ', () => { it('user is able to reply or forward email and check info from composed email', async () => { const senderEmail = CommonData.emailWithMultipleRecipients.sender; + const senderName = CommonData.emailWithMultipleRecipients.senderName; const recipientEmail = CommonData.emailWithMultipleRecipients.recipient; const emailSubject = CommonData.emailWithMultipleRecipients.subject; const emailText = CommonData.emailWithMultipleRecipients.message; @@ -29,17 +30,17 @@ describe('INBOX: ', () => { await MailFolderScreen.clickSearchButton(); await SearchScreen.searchAndClickEmailBySubject(emailSubject); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); // check reply message await EmailScreen.clickReplyButton(); - await NewMessageScreen.checkFilledComposeEmailInfo([senderEmail], replySubject, quoteText); + await NewMessageScreen.checkFilledComposeEmailInfo([senderName], replySubject, quoteText); await NewMessageScreen.clickBackButton(); // check reply all message await EmailScreen.clickMenuButton(); await EmailScreen.clickReplyAllButton(); - await NewMessageScreen.checkFilledComposeEmailInfo([recipientEmail, senderEmail], replySubject, quoteText); + await NewMessageScreen.checkFilledComposeEmailInfo([recipientEmail, senderName], replySubject, quoteText); await NewMessageScreen.clickBackButton(); // check forwarded message diff --git a/appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts b/appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts index c528a51d9..fabb2d16f 100644 --- a/appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts +++ b/appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts @@ -13,7 +13,7 @@ describe('INBOX: ', () => { it('user is able to view encrypted email with attachment', async () => { - const senderEmail = CommonData.sender.email; + const senderName = CommonData.sender.name; const emailSubject = CommonData.encryptedEmailWithAttachment.subject; const emailText = CommonData.encryptedEmailWithAttachment.message; const attachmentName = CommonData.encryptedEmailWithAttachment.attachmentName; @@ -29,7 +29,7 @@ describe('INBOX: ', () => { await MailFolderScreen.clickSearchButton(); await SearchScreen.searchAndClickEmailBySubject(emailSubject); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); await EmailScreen.checkAttachment(encryptedAttachmentName); await driver.terminateApp(bundleId); @@ -47,7 +47,7 @@ describe('INBOX: ', () => { //check attachment after setting correct pass phrase await EmailScreen.enterPassPhrase(correctPassPhrase); await EmailScreen.clickOkButton(); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); await EmailScreen.checkAttachment(encryptedAttachmentName); await EmailScreen.clickOnAttachmentCell(); await AttachmentScreen.checkAttachment(attachmentName); @@ -63,8 +63,8 @@ describe('INBOX: ', () => { await EmailScreen.clickBackButton(); await MailFolderScreen.clickOnEmailBySubject(emailSubject); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); - await EmailScreen.checkAttachment(encryptedAttachmentName); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); + await EmailScreen.checkAttachment(attachmentName); await EmailScreen.clickOnAttachmentCell(); await AttachmentScreen.checkAttachment(attachmentName); diff --git a/appium/tests/specs/live/inbox/ReadEmailAfterRestartApp.spec.ts b/appium/tests/specs/live/inbox/ReadEmailAfterRestartApp.spec.ts index a871e2040..6da810990 100644 --- a/appium/tests/specs/live/inbox/ReadEmailAfterRestartApp.spec.ts +++ b/appium/tests/specs/live/inbox/ReadEmailAfterRestartApp.spec.ts @@ -12,7 +12,7 @@ describe('INBOX: ', () => { it('user is able to see plain email without setting pass phrase after restart app', async () => { - const senderEmail = CommonData.sender.email; + const senderName = CommonData.sender.name; const emailSubject = CommonData.simpleEmail.subject; const emailText = CommonData.simpleEmail.message; @@ -22,7 +22,7 @@ describe('INBOX: ', () => { await MailFolderScreen.clickSearchButton(); await SearchScreen.searchAndClickEmailBySubject(emailSubject); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); await driver.terminateApp(CommonData.bundleId.id); await driver.activateApp(CommonData.bundleId.id); @@ -31,6 +31,6 @@ describe('INBOX: ', () => { await MailFolderScreen.clickSearchButton(); await SearchScreen.searchAndClickEmailBySubject(emailSubject); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); }); }); diff --git a/appium/tests/specs/live/inbox/ReadTextEmail.spec.ts b/appium/tests/specs/live/inbox/ReadTextEmail.spec.ts index 642341ef0..184fe231d 100644 --- a/appium/tests/specs/live/inbox/ReadTextEmail.spec.ts +++ b/appium/tests/specs/live/inbox/ReadTextEmail.spec.ts @@ -12,7 +12,7 @@ describe('INBOX: ', () => { it('user is able to view text email and recipients list', async () => { - const senderEmail = CommonData.recipientsListEmail.sender; + const senderName = CommonData.recipientsListEmail.senderName; const emailSubject = CommonData.recipientsListEmail.subject; const emailText = CommonData.recipientsListEmail.message; const recipientsButton = CommonData.recipientsListEmail.recipients; @@ -27,7 +27,7 @@ describe('INBOX: ', () => { await MailFolderScreen.clickSearchButton(); await SearchScreen.searchAndClickEmailBySubject(emailSubject); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); await EmailScreen.checkRecipientsButton(recipientsButton); await EmailScreen.clickRecipientsButton(); await EmailScreen.checkRecipientsList(toLabel, ccLabel, bccLabel); diff --git a/appium/tests/specs/live/update/CheckAppAfterUpdateFromOldVersion.spec.ts b/appium/tests/specs/live/update/CheckAppAfterUpdateFromOldVersion.spec.ts index 11789c98e..014166ce4 100644 --- a/appium/tests/specs/live/update/CheckAppAfterUpdateFromOldVersion.spec.ts +++ b/appium/tests/specs/live/update/CheckAppAfterUpdateFromOldVersion.spec.ts @@ -26,6 +26,7 @@ describe('UPDATE: ', () => { const firstContactItemName = 'Dmitry at FlowCrypt'; const firstContactEmail = CommonData.contact.email; const senderEmail = CommonData.sender.email; + const senderName = CommonData.sender.name; const emailSubject = CommonData.encryptedEmail.subject; const emailText = CommonData.encryptedEmail.message; @@ -124,6 +125,6 @@ describe('UPDATE: ', () => { await SearchScreen.searchAndClickEmailBySubject(emailSubject); await EmailScreen.enterPassPhrase(correctPassPhrase); await EmailScreen.clickOkButton(); - await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); }); }); From f69be5027c766923548e68e42b6f3619880fb047 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Mon, 21 Mar 2022 08:51:44 -0400 Subject: [PATCH 11/22] #1337 fix local contact provider mock --- FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift b/FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift index 33572c48f..872d3d8a6 100644 --- a/FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift +++ b/FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift @@ -13,8 +13,8 @@ final class LocalContactsProviderMock: LocalContactsProviderType { func searchRecipient(with email: String) async throws -> RecipientWithSortedPubKeys? { nil } - func searchEmails(query: String) throws -> [String] { [] } - + func searchRecipients(query: String) throws -> [Recipient] { [] } + func save(recipient: RecipientWithSortedPubKeys) throws {} func remove(recipient: RecipientWithSortedPubKeys) throws {} From b05cdc45e86fd7358780574ea17f9a2b19262537 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Mon, 21 Mar 2022 08:56:36 -0400 Subject: [PATCH 12/22] #1337 remove unused files --- .../Contacts Service/ContactsService.swift | 69 ------------------- .../Mocks/ContactsServiceMock.swift | 45 ------------ 2 files changed, 114 deletions(-) delete mode 100644 FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift delete mode 100644 FlowCryptAppTests/Mocks/ContactsServiceMock.swift diff --git a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift deleted file mode 100644 index 8c308645d..000000000 --- a/FlowCrypt/Functionality/Services/Contacts Service/ContactsService.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// RecipientsProvider.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 03/08/2020. -// Copyright © 2017-present FlowCrypt a. s. All rights reserved. -// - -import Foundation - -enum ContactsError: Error { - case keyMissing - case unexpected(String) -} - -protocol ContactsServiceType: PublicKeyProvider, ContactsProviderType { -} - -protocol ContactsProviderType { - func findLocalContact(with email: String) async throws -> RecipientWithSortedPubKeys? - func searchLocalContacts(query: String) throws -> [Recipient] - func fetchPubKeys(for recipient: Recipient) async throws -> RecipientWithSortedPubKeys -} - -protocol PublicKeyProvider { - func retrievePubKeys(for email: String) throws -> [String] - func removePubKey(with fingerprint: String, for email: String) throws -} - -// MARK: - PROVIDER - -struct ContactsService: ContactsServiceType { - let localContactsProvider: LocalContactsProviderType - let pubLookup: PubLookup - - init( - localContactsProvider: LocalContactsProviderType, - clientConfiguration: ClientConfiguration - ) { - self.localContactsProvider = localContactsProvider - self.pubLookup = PubLookup(clientConfiguration: clientConfiguration) - } -} - -extension ContactsService: ContactsProviderType { - func findLocalContact(with email: String) async throws -> RecipientWithSortedPubKeys? { - return try await localContactsProvider.searchRecipient(with: email) - } - - func searchLocalContacts(query: String) throws -> [Recipient] { - try localContactsProvider.searchRecipients(query: query) - } - - func fetchPubKeys(for recipient: Recipient) async throws -> RecipientWithSortedPubKeys { - let lookupRecipient = try await pubLookup.lookup(recipient: recipient) - try localContactsProvider.updateKeys(for: lookupRecipient) - return lookupRecipient - } -} - -extension ContactsService: PublicKeyProvider { - func retrievePubKeys(for email: String) throws -> [String] { - try localContactsProvider.retrievePubKeys(for: email) - } - - func removePubKey(with fingerprint: String, for email: String) throws { - try localContactsProvider.removePubKey(with: fingerprint, for: email) - } -} diff --git a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift deleted file mode 100644 index ee3c70d37..000000000 --- a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// ContactsServiceMock.swift -// FlowCryptAppTests -// -// Created by Anton Kharchevskyi on 25.07.2021. -// Copyright © 2017-present FlowCrypt a. s. All rights reserved. -// - -@testable import FlowCrypt -import Foundation - -final class ContactsServiceMock: ContactsServiceType { - var retrievePubKeysResult: ((String) -> ([String]))! - func retrievePubKeys(for email: String) -> [String] { - retrievePubKeysResult(email) - } - - var fetchContactResult: Result! - func fetchPubKeys(for recipient: Recipient) async throws -> RecipientWithSortedPubKeys { - switch fetchContactResult { - case .success(let result): - return result - case .failure(let error): - throw error - default: - fatalError() - } - } - - var findLocalContactResult: Result! - func findLocalContact(with email: String) async throws -> RecipientWithSortedPubKeys? { - switch findLocalContactResult { - case .success(let result): - return result - case .failure(let error): - throw error - default: - fatalError() - } - } - - func searchLocalContacts(query: String) throws -> [Recipient] { [] } - - func removePubKey(with fingerprint: String, for email: String) {} -} From cd7f37b7b8bd65070f2a274ebbaaabf6742c8219 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Mon, 21 Mar 2022 09:31:43 -0400 Subject: [PATCH 13/22] #1337 fix lint issue --- appium/tests/screenobjects/email.screen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/tests/screenobjects/email.screen.ts b/appium/tests/screenobjects/email.screen.ts index ec3708b96..4c4f52ace 100644 --- a/appium/tests/screenobjects/email.screen.ts +++ b/appium/tests/screenobjects/email.screen.ts @@ -118,7 +118,7 @@ class EmailScreen extends BaseScreen { return $(SELECTORS.ATTACHMENT_TEXT_VIEW); } - checkEmailSender = async (sender: string, index: number = 0) => { + checkEmailSender = async (sender: string, index = 0) => { const element = await this.senderEmail(index); await (await element).waitForDisplayed(); await expect(await (await element).getValue()).toEqual(sender); From a2555376fcd75ed918b3f9907b9e613ade0ffaf8 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Mon, 21 Mar 2022 11:05:27 -0400 Subject: [PATCH 14/22] #1337 fix check reply and forward test --- appium/tests/data/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/tests/data/index.ts b/appium/tests/data/index.ts index 795653ab7..bfd2119c6 100644 --- a/appium/tests/data/index.ts +++ b/appium/tests/data/index.ts @@ -69,7 +69,7 @@ export const CommonData = { }, emailWithMultipleRecipientsWithCC: { sender: 'ioan@flowcrypt.com', - senderName: 'FlowCrypt Compatibility', + senderName: 'Ioan at FlowCrypt', recipient: 'robot@flowcrypt.com', cc: 'robot+cc@flowcrypt.com', subject: 'Message with cc and multiple recipients and attachment', From 1a7ea5903ea1a024441aefb518b5235f8b9c25e5 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Mon, 21 Mar 2022 11:28:40 -0400 Subject: [PATCH 15/22] #1337 fix check thread test --- appium/config/wdio.live.conf.js | 3 +++ appium/tests/data/index.ts | 1 + appium/tests/screenobjects/mail-folder.screen.ts | 2 +- appium/tests/specs/live/inbox/CheckThreadRendering.spec.ts | 6 +++--- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/appium/config/wdio.live.conf.js b/appium/config/wdio.live.conf.js index 3075d83a9..8830f1a56 100644 --- a/appium/config/wdio.live.conf.js +++ b/appium/config/wdio.live.conf.js @@ -21,6 +21,9 @@ config.suites = { ], update: [ './tests/specs/live/update/*.spec.ts' + ], + test: [ + './tests/specs/live/inbox/CheckThreadRendering.spec.ts' ] }; diff --git a/appium/tests/data/index.ts b/appium/tests/data/index.ts index bfd2119c6..54a34d783 100644 --- a/appium/tests/data/index.ts +++ b/appium/tests/data/index.ts @@ -23,6 +23,7 @@ export const CommonData = { threadMessage: { subject: 'test thread rendering', sender: 'dmitry@flowcrypt.com', + senderName: 'Dmitry at FlowCrypt', firstThreadMessage: 'first message', secondThreadMessage: 'Second thread rendering message\n' + '\n' + diff --git a/appium/tests/screenobjects/mail-folder.screen.ts b/appium/tests/screenobjects/mail-folder.screen.ts index 225bbaf2c..532af1813 100644 --- a/appium/tests/screenobjects/mail-folder.screen.ts +++ b/appium/tests/screenobjects/mail-folder.screen.ts @@ -72,7 +72,7 @@ class MailFolderScreen extends BaseScreen { clickOnEmailBySubject = async (subject: string) => { const selector = `~${subject}`; - if (await (await $(selector)).isDisplayed() !== true) { + if (!await (await $(selector)).isDisplayed()) { await TouchHelper.scrollDownToElement(await $(selector)); } await ElementHelper.waitAndClick(await $(selector), 500); diff --git a/appium/tests/specs/live/inbox/CheckThreadRendering.spec.ts b/appium/tests/specs/live/inbox/CheckThreadRendering.spec.ts index c6416acac..422bc03f6 100644 --- a/appium/tests/specs/live/inbox/CheckThreadRendering.spec.ts +++ b/appium/tests/specs/live/inbox/CheckThreadRendering.spec.ts @@ -9,7 +9,7 @@ import { CommonData } from '../../../data'; describe('INBOX: ', () => { it('check thread rendering', async () => { - const senderEmail = CommonData.threadMessage.sender; + const senderName = CommonData.threadMessage.senderName; const emailSubject = CommonData.threadMessage.subject; const firstMessage = CommonData.threadMessage.firstThreadMessage; const secondMessage = CommonData.threadMessage.secondThreadMessage; @@ -24,8 +24,8 @@ describe('INBOX: ', () => { await MailFolderScreen.checkInboxScreen(); await MailFolderScreen.clickOnEmailBySubject(emailSubject); - await EmailScreen.checkThreadMessage(senderEmail, emailSubject, thirdMessage, dateThird, 2); + await EmailScreen.checkThreadMessage(senderName, emailSubject, thirdMessage, dateThird, 2); await EmailScreen.checkThreadMessage(userEmail, emailSubject, secondMessage, dateSecond, 1); - await EmailScreen.checkThreadMessage(senderEmail, emailSubject, firstMessage, dateFirst); + await EmailScreen.checkThreadMessage(senderName, emailSubject, firstMessage, dateFirst); }); }); From b1fd927831f68f3cc21faa0698b849e70633e751 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Mon, 21 Mar 2022 11:40:24 -0400 Subject: [PATCH 16/22] #1337 fix attachment test --- appium/config/wdio.live.conf.js | 3 --- appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/appium/config/wdio.live.conf.js b/appium/config/wdio.live.conf.js index 8830f1a56..3075d83a9 100644 --- a/appium/config/wdio.live.conf.js +++ b/appium/config/wdio.live.conf.js @@ -21,9 +21,6 @@ config.suites = { ], update: [ './tests/specs/live/update/*.spec.ts' - ], - test: [ - './tests/specs/live/inbox/CheckThreadRendering.spec.ts' ] }; diff --git a/appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts b/appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts index fabb2d16f..79cfb4556 100644 --- a/appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts +++ b/appium/tests/specs/live/inbox/ReadAttachmentEmail.spec.ts @@ -64,7 +64,7 @@ describe('INBOX: ', () => { await MailFolderScreen.clickOnEmailBySubject(emailSubject); await EmailScreen.checkOpenedEmail(senderName, emailSubject, emailText); - await EmailScreen.checkAttachment(attachmentName); + await EmailScreen.checkAttachment(encryptedAttachmentName); await EmailScreen.clickOnAttachmentCell(); await AttachmentScreen.checkAttachment(attachmentName); From e09f9282011e1da1f0e337d7bdca3af40b2b9cff Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Tue, 22 Mar 2022 10:06:01 -0400 Subject: [PATCH 17/22] #1337 fix draft recipients evaulate issue --- .../Compose/ComposeViewController.swift | 46 +++++++++---------- .../Mocks/LocalContactsProviderMock.swift | 2 +- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 12fac5b22..c19fdc787 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -204,16 +204,28 @@ final class ComposeViewController: TableNodeViewController { func update(with message: Message) { self.contextToSend.subject = message.subject self.contextToSend.message = message.raw - self.contextToSend.recipients = [ - ComposeMessageRecipient( - email: "tom@flowcrypt.com", - name: "Tom", - type: .to, - state: decorator.recipientIdleState - ) - ] + message.to.forEach { recipient in + evaluateMessage(recipient: recipient, type: .to) + } + message.cc.forEach { recipient in + evaluateMessage(recipient: recipient, type: .cc) + } + message.bcc.forEach { recipient in + evaluateMessage(recipient: recipient, type: .bcc) + } } + func evaluateMessage(recipient: Recipient, type: RecipientType) { + let recipient = ComposeMessageRecipient( + email: recipient.email, + name: recipient.name, + type: .to, + state: decorator.recipientIdleState + ) + contextToSend.add(recipient: recipient) + evaluate(recipient: recipient) + } + private func observeComposeUpdates() { composeMessageService.onStateChanged { [weak self] state in DispatchQueue.main.async { @@ -333,25 +345,11 @@ extension ComposeViewController { guard input.isQuote else { return } input.quoteRecipients.forEach { recipient in - let recipient = ComposeMessageRecipient( - email: recipient.email, - name: recipient.name, - type: .to, - state: decorator.recipientIdleState - ) - contextToSend.add(recipient: recipient) - evaluate(recipient: recipient) + evaluateMessage(recipient: recipient, type: .to) } input.quoteCCRecipients.forEach { recipient in - let recipient = ComposeMessageRecipient( - email: recipient.email, - name: recipient.name, - type: .cc, - state: decorator.recipientIdleState - ) - contextToSend.add(recipient: recipient) - evaluate(recipient: recipient) + evaluateMessage(recipient: recipient, type: .cc) } if input.quoteCCRecipients.isNotEmpty { diff --git a/FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift b/FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift index 872d3d8a6..3706d6c6a 100644 --- a/FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift +++ b/FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift @@ -14,7 +14,7 @@ final class LocalContactsProviderMock: LocalContactsProviderType { func searchRecipient(with email: String) async throws -> RecipientWithSortedPubKeys? { nil } func searchRecipients(query: String) throws -> [Recipient] { [] } - + func save(recipient: RecipientWithSortedPubKeys) throws {} func remove(recipient: RecipientWithSortedPubKeys) throws {} From 67dfc233674125f3a69d7538e35d580cf46d80af Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Tue, 22 Mar 2022 10:57:39 -0400 Subject: [PATCH 18/22] #1337 use MCOAddress to parse mailbox address --- .../Compose/ComposeViewController.swift | 2 +- FlowCrypt/Models/Common/Recipient.swift | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index c19fdc787..671c0bd3e 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -225,7 +225,7 @@ final class ComposeViewController: TableNodeViewController { contextToSend.add(recipient: recipient) evaluate(recipient: recipient) } - + private func observeComposeUpdates() { composeMessageService.onStateChanged { [weak self] state in DispatchQueue.main.async { diff --git a/FlowCrypt/Models/Common/Recipient.swift b/FlowCrypt/Models/Common/Recipient.swift index 3788a70f5..af175bcd3 100644 --- a/FlowCrypt/Models/Common/Recipient.swift +++ b/FlowCrypt/Models/Common/Recipient.swift @@ -7,6 +7,7 @@ // import Foundation +import MailCore import GoogleAPIClientForREST_PeopleService struct Recipient: RecipientBase { @@ -25,20 +26,13 @@ extension Recipient { } init(_ string: String) { - let parts = string.components(separatedBy: " ") - - guard parts.count > 1, let email = parts.last else { + guard let address = MCOAddress.init(nonEncodedRFC822String: string) else { self.name = nil self.email = string return } - - self.email = email.filter { !["<", ">"].contains($0) } - let name = string - .replacingOccurrences(of: email, with: "") - .replacingOccurrences(of: "\"", with: "") - .trimmingCharacters(in: .whitespaces) - self.name = name == self.email ? nil : name + self.name = address.displayName + self.email = address.mailbox } init?(person: GTLRPeopleService_Person) { From 384acab7792781b01a5662c5fc7e0463e760f9fb Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Tue, 22 Mar 2022 12:32:17 -0400 Subject: [PATCH 19/22] #1337 fix do not use email for name in recipient --- .../Contacts Service/Models/RecipientWithSortedPubKeys.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift b/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift index cb751a3c7..1f5af0228 100644 --- a/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift +++ b/FlowCrypt/Functionality/Services/Contacts Service/Models/RecipientWithSortedPubKeys.swift @@ -26,7 +26,7 @@ struct RecipientWithSortedPubKeys: RecipientBase { extension RecipientWithSortedPubKeys { init(_ recipient: Recipient, keyDetails: [KeyDetails] = []) { self.email = recipient.email - self.name = recipient.name ?? keyDetails.first?.users.first ?? email + self.name = recipient.name ?? keyDetails.first?.users.first self.lastUsed = recipient.lastUsed self._pubKeys = keyDetails.map(PubKey.init) } From 500715b365565b713c67b664dbf035974965e5fd Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Wed, 23 Mar 2022 08:50:26 -0400 Subject: [PATCH 20/22] #1337 fix evaluate recipient issue --- .../Controllers/Compose/ComposeViewController.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 671c0bd3e..193aa699d 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -197,7 +197,7 @@ final class ComposeViewController: TableNodeViewController { private func evaluateAllRecipients() { for recipient in contextToSend.recipients { - evaluate(recipient: recipient) + evaluate(recipient: recipient, showRecipientLabelFlag: false) } } @@ -1117,7 +1117,7 @@ extension ComposeViewController { } } - private func evaluate(recipient: ComposeMessageRecipient) { + private func evaluate(recipient: ComposeMessageRecipient, showRecipientLabelFlag: Bool = true) { guard recipient.email.isValidEmail else { updateRecipient( email: recipient.email, @@ -1139,11 +1139,15 @@ extension ComposeViewController { let contactWithFetchedKeys = try await service.fetchPubKeys(for: contact) handleEvaluation(for: contactWithFetchedKeys) isRecipientLoading = false - showRecipientLabelIfNecessary() + if showRecipientLabelFlag { + showRecipientLabelIfNecessary() + } } catch { handleEvaluation(error: error, with: recipient.email, contact: localContact) isRecipientLoading = false - showRecipientLabelIfNecessary() + if showRecipientLabelFlag { + showRecipientLabelIfNecessary() + } } } } From 0d35bfc7dc55f6b311e7cef87b3f7f28f41dfadf Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Wed, 23 Mar 2022 09:23:46 -0400 Subject: [PATCH 21/22] fix: reply all crash issue --- .../Compose/ComposeViewController.swift | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 193aa699d..d8ea1390a 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -49,7 +49,6 @@ final class ComposeViewController: TableNodeViewController { } private var userFinishedSearching = false - private var isRecipientLoading = false private var userTappedOutSideRecipientsArea = false private var shouldShowEmailRecipientsLabel = false private let appContext: AppContextWithUser @@ -197,7 +196,7 @@ final class ComposeViewController: TableNodeViewController { private func evaluateAllRecipients() { for recipient in contextToSend.recipients { - evaluate(recipient: recipient, showRecipientLabelFlag: false) + evaluate(recipient: recipient) } } @@ -717,7 +716,8 @@ extension ComposeViewController { } private func showRecipientLabelIfNecessary() { - guard !self.isRecipientLoading, + let isRecipientLoading = self.contextToSend.recipients.filter { $0.state == decorator.recipientIdleState }.isNotEmpty + guard !isRecipientLoading, self.contextToSend.recipients.isNotEmpty, self.userTappedOutSideRecipientsArea else { return @@ -1117,7 +1117,7 @@ extension ComposeViewController { } } - private func evaluate(recipient: ComposeMessageRecipient, showRecipientLabelFlag: Bool = true) { + private func evaluate(recipient: ComposeMessageRecipient) { guard recipient.email.isValidEmail else { updateRecipient( email: recipient.email, @@ -1127,7 +1127,6 @@ extension ComposeViewController { } Task { - isRecipientLoading = true var localContact: RecipientWithSortedPubKeys? do { if let contact = try await service.findLocalContact(with: recipient.email) { @@ -1138,16 +1137,10 @@ extension ComposeViewController { let contact = Recipient(recipient: recipient) let contactWithFetchedKeys = try await service.fetchPubKeys(for: contact) handleEvaluation(for: contactWithFetchedKeys) - isRecipientLoading = false - if showRecipientLabelFlag { - showRecipientLabelIfNecessary() - } + showRecipientLabelIfNecessary() } catch { handleEvaluation(error: error, with: recipient.email, contact: localContact) - isRecipientLoading = false - if showRecipientLabelFlag { - showRecipientLabelIfNecessary() - } + showRecipientLabelIfNecessary() } } } From c2342e306a964cf775ba859020a6bd043b35b801 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Wed, 23 Mar 2022 09:29:19 -0400 Subject: [PATCH 22/22] #1337 fix invalid type in compose view --- FlowCrypt/Controllers/Compose/ComposeViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index d8ea1390a..5d8aaa5ac 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -218,7 +218,7 @@ final class ComposeViewController: TableNodeViewController { let recipient = ComposeMessageRecipient( email: recipient.email, name: recipient.name, - type: .to, + type: type, state: decorator.recipientIdleState ) contextToSend.add(recipient: recipient)