From 3697f074e161d3af8894ef16200ebc3d2dd7f491 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 11 Oct 2021 17:10:10 +0300 Subject: [PATCH 01/10] issue #553 store multiple keys per contact --- FlowCrypt.xcodeproj/project.pbxproj | 8 +++ .../xcshareddata/swiftpm/Package.resolved | 9 ---- .../ComposeMessageService.swift | 10 ++-- .../Contacts Services/ContactsService.swift | 8 +-- .../LocalContactsProvider.swift | 24 +++++---- .../Contacts Services/Models/Contact.swift | 52 +++---------------- .../Contacts Services/Models/ContactKey.swift | 51 ++++++++++++++++++ .../Functionality/WKDURLs/PubLookup.swift | 9 +--- .../Realm Models/ContactKeyObject.swift | 21 ++++++++ .../Models/Realm Models/ContactObject.swift | 52 ++++--------------- 10 files changed, 120 insertions(+), 124 deletions(-) create mode 100644 FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift create mode 100644 FlowCrypt/Models/Realm Models/ContactKeyObject.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index d5cc25c4b..8a0096a66 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -55,6 +55,8 @@ 510A260126FDFEBE00163271 /* MailCore2 in Frameworks */ = {isa = PBXBuildFile; productRef = 510A260026FDFEBE00163271 /* MailCore2 */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; + 51B4AE51271444580001F33B /* ContactKeyObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE50271444580001F33B /* ContactKeyObject.swift */; }; + 51B4AE5327144E590001F33B /* ContactKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE5227144E590001F33B /* ContactKey.swift */; }; 51E13F12270F92BA00F287CA /* IDZSwiftCommonCrypto in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */; }; 51E13F15270F92F200F287CA /* BigInt in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F14270F92F200F287CA /* BigInt */; }; 51E13F18270F934C00F287CA /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F17270F934C00F287CA /* MBProgressHUD */; }; @@ -465,6 +467,8 @@ 50531BE32629B9A80039BAE9 /* AttachmentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentNode.swift; sourceTree = ""; }; 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrvKeyInfoTests.swift; sourceTree = ""; }; 51775C38270C7D2400D7C944 /* StorageMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageMethod.swift; sourceTree = ""; }; + 51B4AE50271444580001F33B /* ContactKeyObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyObject.swift; sourceTree = ""; }; + 51B4AE5227144E590001F33B /* ContactKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKey.swift; sourceTree = ""; }; 51E1673C270DAFF900D27C52 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; 55652F68438D6EDFE71EA13C /* Pods-FlowCryptUIApplication.enterprise.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUIApplication.enterprise.xcconfig"; path = "Target Support Files/Pods-FlowCryptUIApplication/Pods-FlowCryptUIApplication.enterprise.xcconfig"; sourceTree = ""; }; 5A39F42C239EC321001F4607 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; @@ -1081,6 +1085,7 @@ children = ( D2F41372243CC7990066AFB5 /* UserObject.swift */, D27B911C24EFE806002DF0A1 /* ContactObject.swift */, + 51B4AE50271444580001F33B /* ContactKeyObject.swift */, D274724324FD1932006BA6EF /* FolderObject.swift */, 04B4728B1ECE29D200B8266F /* KeyInfo.swift */, D2F41370243CC76E0066AFB5 /* SessionObject.swift */, @@ -1793,6 +1798,7 @@ isa = PBXGroup; children = ( D27B911E24EFE828002DF0A1 /* Contact.swift */, + 51B4AE5227144E590001F33B /* ContactKey.swift */, ); path = Models; sourceTree = ""; @@ -2479,6 +2485,7 @@ 9FB22CE425715D3E0026EE64 /* GmailServiceErrorHandler.swift in Sources */, 9F4163E6266520B600106194 /* CommonNodesInputs.swift in Sources */, 04B472951ECE29F600B8266F /* MyMenuViewController.swift in Sources */, + 51B4AE5327144E590001F33B /* ContactKey.swift in Sources */, 21623D1826FA860700A11B9A /* PhotosManager.swift in Sources */, C132B9B41EC2DBD800763715 /* AppDelegate.swift in Sources */, 21489B7C267CBA0E00BDE4AC /* ClientConfiguration.swift in Sources */, @@ -2612,6 +2619,7 @@ 9FF0671025520D7100FCC9E6 /* MessageGateway.swift in Sources */, 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */, 9F79229426696B9300DA3D80 /* KeyDataStorage.swift in Sources */, + 51B4AE51271444580001F33B /* ContactKeyObject.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewContainerController.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */, diff --git a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8d66041a3..816067881 100644 --- a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -46,15 +46,6 @@ "version": "1.7.0" } }, - { - "package": "GTMAppAuth", - "repositoryURL": "https://github.com/google/GTMAppAuth", - "state": { - "branch": null, - "revision": "40f4103fb52109032c05599a0c39ad43edbdf80a", - "version": "1.2.2" - } - }, { "package": "IDZSwiftCommonCrypto", "repositoryURL": "https://github.com/iosdevzone/IDZSwiftCommonCrypto", diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index 6923e4a2d..b5b6ee8e1 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -111,18 +111,18 @@ final class ComposeMessageService { } } - private func getPubKeys(for recepients: [ComposeMessageRecipient]) -> Result<[String], MessageValidationError> { - let pubKeys = recepients.map { - ($0.email, contactsService.retrievePubKey(for: $0.email)) + private func getPubKeys(for recipients: [ComposeMessageRecipient]) -> Result<[String], MessageValidationError> { + let pubKeys = recipients.map { + ($0.email, contactsService.retrievePubKeys(for: $0.email)) } - let emailsWithoutPubKeys = pubKeys.filter { $0.1 == nil }.map(\.0) + let emailsWithoutPubKeys = pubKeys.filter { $0.1.isEmpty }.map(\.0) guard emailsWithoutPubKeys.isEmpty else { return .failure(.noPubRecipients(emailsWithoutPubKeys)) } - return .success(pubKeys.compactMap(\.1)) + return .success(pubKeys.filter({ $0.1.isNotEmpty }).flatMap(\.1)) } // MARK: - Encrypt and Send diff --git a/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift index 4cfbf678f..32ba57354 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift @@ -22,7 +22,7 @@ protocol ContactsProviderType { } protocol PublicKeyProvider { - func retrievePubKey(for email: String) -> String? + func retrievePubKeys(for email: String) -> [String] } // MARK: - PROVIDER @@ -58,9 +58,9 @@ extension ContactsService: ContactsProviderType { } extension ContactsService: PublicKeyProvider { - func retrievePubKey(for email: String) -> String? { - let publicKey = localContactsProvider.retrievePubKey(for: email) + func retrievePubKeys(for email: String) -> [String] { + let publicKeys = localContactsProvider.retrievePubKeys(for: email) localContactsProvider.updateLastUsedDate(for: email) - return publicKey + return publicKeys } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift index 75d0cc111..db6feb9af 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift @@ -42,11 +42,12 @@ extension LocalContactsProvider: LocalContactsProviderType { } } - func retrievePubKey(for email: String) -> String? { + func retrievePubKeys(for email: String) -> [String] { localContactsCache.encryptedStorage.storage .objects(ContactObject.self) .first(where: { $0.email == email })? - .pubKey + .pubKeys + .map { $0.key } ?? [] } func save(contact: Contact) { @@ -68,14 +69,15 @@ extension LocalContactsProvider: LocalContactsProviderType { } func getAllContacts() -> [Contact] { - Array( - localContactsCache.realm - .objects(ContactObject.self) - .map { - let keyDetail = try? core.parseKeys(armoredOrBinary: $0.pubKey.data()).keyDetails.first - return Contact($0, keyDetail: keyDetail) - } - .sorted(by: { $0.email > $1.email }) - ) + localContactsCache.realm + .objects(ContactObject.self) + .map { object in + let keyDetails = object + .pubKeys + .compactMap { try? core.parseKeys(armoredOrBinary: $0.key.data()).keyDetails } + .flatMap { $0 } + return Contact(object, keyDetails: Array(keyDetails)) + } + .sorted(by: { $0.email > $1.email }) } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift b/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift index 64510ba91..3238a86e6 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift @@ -12,65 +12,27 @@ struct Contact { let email: String /// name if known let name: String? - /// public key - let pubKey: String - /// will be provided later - let pubKeyLastSig: Date? - /// the date when pubkey was retrieved from Attester, or nil - let pubkeyLastChecked: Date? - /// pubkey expiration date - let pubkeyExpiresOn: Date? - /// all pubkey longids, comma-separated - let longids: [String] - var longid: String? { longids.first } - + /// public keys + let pubKeys: [ContactKey] /// last time an email was sent to this contact, update when email is sent let lastUsed: Date? - - /// all pubkey fingerprints, comma-separated - let fingerprints: [String] - /// first pubkey fingerprint - var fingerprint: String? { fingerprints.first } - - /// pubkey created date - let pubkeyCreated: Date? - - let algo: KeyAlgo? } extension Contact { - init(_ contactObject: ContactObject, keyDetail: KeyDetails? = nil) { + init(_ contactObject: ContactObject, keyDetails: [KeyDetails] = []) { self.email = contactObject.email self.name = contactObject.name.nilIfEmpty - self.pubKey = contactObject.pubKey - self.pubKeyLastSig = contactObject.pubKeyLastSig - self.pubkeyLastChecked = contactObject.pubkeyLastChecked - self.pubkeyExpiresOn = contactObject.pubkeyExpiresOn + self.pubKeys = keyDetails.map(ContactKey.init) self.lastUsed = contactObject.lastUsed - self.longids = contactObject.longids.map(\.value) - self.fingerprints = contactObject.fingerprints.split(separator: ",").map(String.init) - self.pubkeyCreated = contactObject.pubkeyCreated - self.algo = keyDetail?.algo } } extension Contact { - init(email: String, keyDetail: KeyDetails) { - let keyIds = keyDetail.ids - let longids = keyIds.map(\.longid) - let fingerprints = keyIds.map(\.fingerprint) - + init(email: String, keyDetails: [KeyDetails]) { self.email = email - self.name = keyDetail.users.first ?? email - self.pubKey = keyDetail.public - self.pubKeyLastSig = keyDetail.lastModified.map { Date(timeIntervalSince1970: TimeInterval($0)) } - self.pubkeyLastChecked = Date() - self.pubkeyExpiresOn = keyDetail.expiration.map { Date(timeIntervalSince1970: TimeInterval($0)) } - self.longids = longids + self.name = keyDetails.first?.users.first ?? email self.lastUsed = nil - self.fingerprints = fingerprints - self.pubkeyCreated = Date(timeIntervalSince1970: Double(keyDetail.created)) - self.algo = keyDetail.algo + self.pubKeys = keyDetails.map(ContactKey.init) } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift b/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift new file mode 100644 index 000000000..9e11172cc --- /dev/null +++ b/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift @@ -0,0 +1,51 @@ +// +// ContactKey.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 11/10/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct ContactKey { + let key: String + /// will be provided later + let lastSig: Date? + /// the date when key was retrieved from Attester, or nil + let lastChecked: Date? + /// expiration date + let expiresOn: Date? + /// all key longids, comma-separated + let longids: [String] + /// all key fingerprints, comma-separated + let fingerprints: [String] + /// key created date + let created: Date? + /// key algo + let algo: KeyAlgo? +} + +extension ContactKey { + /// first key longid + var longid: String? { longids.first } + /// first key fingerprint + var fingerprint: String? { fingerprints.first } +} + +extension ContactKey { + init(keyDetails: KeyDetails) { + let keyIds = keyDetails.ids + let longids = keyIds.map(\.longid) + let fingerprints = keyIds.map(\.fingerprint) + + self.init(key: keyDetails.public, + lastSig: keyDetails.lastModified.map { Date(timeIntervalSince1970: TimeInterval($0)) }, + lastChecked: Date(), + expiresOn: keyDetails.expiration.map { Date(timeIntervalSince1970: TimeInterval($0)) }, + longids: longids, + fingerprints: fingerprints, + created: Date(timeIntervalSince1970: Double(keyDetails.created)), + algo: keyDetails.algo) + } +} diff --git a/FlowCrypt/Functionality/WKDURLs/PubLookup.swift b/FlowCrypt/Functionality/WKDURLs/PubLookup.swift index ab7b4201b..8d5d83bb1 100644 --- a/FlowCrypt/Functionality/WKDURLs/PubLookup.swift +++ b/FlowCrypt/Functionality/WKDURLs/PubLookup.swift @@ -27,14 +27,7 @@ class PubLookup: PubLookupType { func lookup(with email: String) -> Promise { Promise { resolve, _ in let keyDetails = try awaitPromise(self.getKeyDetails(email)) - // TODO: - we are blindly choosing .first public key, in the future we should return [Contact] - // then eg encrypt for all returned Contacts - // also stop throwing below - no point. Return - // empty array then handle downstream - guard let keyDetail = keyDetails.first else { - throw ContactsError.keyMissing - } - resolve(Contact(email: email, keyDetail: keyDetail)) + resolve(Contact(email: email, keyDetails: keyDetails)) } } diff --git a/FlowCrypt/Models/Realm Models/ContactKeyObject.swift b/FlowCrypt/Models/Realm Models/ContactKeyObject.swift new file mode 100644 index 000000000..5d27c50c8 --- /dev/null +++ b/FlowCrypt/Models/Realm Models/ContactKeyObject.swift @@ -0,0 +1,21 @@ +// +// ContactKeyObject.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 11/10/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation +import RealmSwift + +final class ContactKeyObject: Object { + @Persisted var key: String = "" + + @Persisted var lastSig: Date? + @Persisted var lastChecked: Date? + @Persisted var expiresOn: Date? + @Persisted var longids: List + @Persisted var fingerprints: List + @Persisted var created: Date? +} diff --git a/FlowCrypt/Models/Realm Models/ContactObject.swift b/FlowCrypt/Models/Realm Models/ContactObject.swift index 3aec666e8..ea1def172 100644 --- a/FlowCrypt/Models/Realm Models/ContactObject.swift +++ b/FlowCrypt/Models/Realm Models/ContactObject.swift @@ -10,7 +10,7 @@ import Foundation import RealmSwift final class LongId: Object { - @objc dynamic var value: String = "" + @Persisted var value: String = "" convenience init(value: String) { self.init() @@ -19,55 +19,29 @@ final class LongId: Object { } final class ContactObject: Object { - @objc dynamic var email: String = "" - @objc dynamic var pubKey: String = "" + @Persisted(primaryKey: true) var email: String = "" - @objc dynamic var name: String? - - @objc dynamic var pubkeyExpiresOn: Date? - @objc dynamic var pubKeyLastSig: Date? - @objc dynamic var pubkeyLastChecked: Date? - @objc dynamic var pubkeyCreated: Date? - @objc dynamic var lastUsed: Date? - - /// all pubkey fingerprints, comma-separated - @objc dynamic var fingerprints: String = "" - - let longids = List() + @Persisted var name: String? + @Persisted var lastUsed: Date? + @Persisted var pubKeys = List() convenience init( email: String, name: String?, - pubKey: String, - pubKeyLastSig: Date?, - pubkeyLastChecked: Date?, - pubkeyExpiresOn: Date?, lastUsed: Date?, - pubkeyCreated: Date?, - longids: [String], - fingerprints: [String] + keys: [ContactKey] ) { self.init() self.email = email self.name = name ?? "" - self.pubKey = pubKey - self.pubkeyExpiresOn = pubkeyExpiresOn - self.pubKeyLastSig = pubKeyLastSig - self.pubkeyLastChecked = pubkeyLastChecked - self.pubkeyCreated = pubkeyCreated self.lastUsed = lastUsed - self.fingerprints = fingerprints.joined(separator: ",") - longids - .map(LongId.init) + keys + .map(ContactKeyObject.init) .forEach { - self.longids.append($0) + self.pubKeys.append($0) } } - - override class func primaryKey() -> String? { - "email" - } } extension ContactObject { @@ -75,14 +49,8 @@ extension ContactObject { self.init( email: contact.email, name: contact.name, - pubKey: contact.pubKey, - pubKeyLastSig: contact.pubKeyLastSig, - pubkeyLastChecked: contact.pubkeyLastChecked, - pubkeyExpiresOn: contact.pubkeyExpiresOn, lastUsed: contact.lastUsed, - pubkeyCreated: contact.pubkeyCreated, - longids: contact.longids, - fingerprints: contact.fingerprints + keys: contact.pubKeys ) } } From 919cf6e1ec4d3b6f972f54c0efd9ef34743fc4ff Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 12 Oct 2021 15:53:17 +0300 Subject: [PATCH 02/10] issue #553 update contact detail screen --- FlowCrypt.xcodeproj/project.pbxproj | 14 ++- .../xcshareddata/swiftpm/Package.resolved | 9 ++ .../ContactDetailDecorator.swift | 30 +++--- .../ContactDetailViewController.swift | 53 ++++++---- .../Contacts List/ContactsListDecorator.swift | 3 + .../Realm Models/ContactKeyObject.swift | 35 ++++++- FlowCrypt/Resources/Localizable.stringsdict | 18 ++++ .../Resources/en.lproj/Localizable.strings | 2 +- FlowCryptUI/Cell Nodes/ContactCellNode.swift | 14 ++- .../Cell Nodes/ContactDetailNode.swift | 98 ------------------- .../Cell Nodes/ContactKeyCellNode.swift | 58 +++++++++++ .../Cell Nodes/ContactUserCellNode.swift | 43 ++++++++ Podfile.lock | 4 +- 13 files changed, 235 insertions(+), 146 deletions(-) delete mode 100644 FlowCryptUI/Cell Nodes/ContactDetailNode.swift create mode 100644 FlowCryptUI/Cell Nodes/ContactKeyCellNode.swift create mode 100644 FlowCryptUI/Cell Nodes/ContactUserCellNode.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 5b9bb7682..fa3731188 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -56,9 +56,10 @@ 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */ = {isa = PBXBuildFile; productRef = 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; + 51B4AE4F27137CB70001F33B /* Promises in Frameworks */ = {isa = PBXBuildFile; productRef = 51B4AE4E27137CB70001F33B /* Promises */; }; 51B4AE51271444580001F33B /* ContactKeyObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE50271444580001F33B /* ContactKeyObject.swift */; }; 51B4AE5327144E590001F33B /* ContactKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE5227144E590001F33B /* ContactKey.swift */; }; - 51B4AE4F27137CB70001F33B /* Promises in Frameworks */ = {isa = PBXBuildFile; productRef = 51B4AE4E27137CB70001F33B /* Promises */; }; + 51DE2FEE2714DA0400916222 /* ContactKeyCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DE2FED2714DA0400916222 /* ContactKeyCellNode.swift */; }; 51E13F12270F92BA00F287CA /* IDZSwiftCommonCrypto in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */; }; 51E13F15270F92F200F287CA /* BigInt in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F14270F92F200F287CA /* BigInt */; }; 51E13F18270F934C00F287CA /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F17270F934C00F287CA /* MBProgressHUD */; }; @@ -328,7 +329,7 @@ D2E26F6824F169E300612AF1 /* ContactCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E26F6724F169E300612AF1 /* ContactCellNode.swift */; }; D2E26F6C24F25B1F00612AF1 /* KeyAlgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E26F6B24F25B1F00612AF1 /* KeyAlgo.swift */; }; D2E26F7024F266F300612AF1 /* ContactDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E26F6F24F266F300612AF1 /* ContactDetailViewController.swift */; }; - D2E26F7224F26FFF00612AF1 /* ContactDetailNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E26F7124F26FFF00612AF1 /* ContactDetailNode.swift */; }; + D2E26F7224F26FFF00612AF1 /* ContactUserCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E26F7124F26FFF00612AF1 /* ContactUserCellNode.swift */; }; D2E26F7424F2705B00612AF1 /* ContactDetailDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E26F7324F2705B00612AF1 /* ContactDetailDecorator.swift */; }; D2F18464244B0C63000CC5D1 /* SignInGoogleTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F18463244B0C63000CC5D1 /* SignInGoogleTest.swift */; }; D2F18468244B0F35000CC5D1 /* AppTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F18467244B0F35000CC5D1 /* AppTestHelper.swift */; }; @@ -469,6 +470,7 @@ 51775C38270C7D2400D7C944 /* StorageMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageMethod.swift; sourceTree = ""; }; 51B4AE50271444580001F33B /* ContactKeyObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyObject.swift; sourceTree = ""; }; 51B4AE5227144E590001F33B /* ContactKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKey.swift; sourceTree = ""; }; + 51DE2FED2714DA0400916222 /* ContactKeyCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyCellNode.swift; sourceTree = ""; }; 51E1673C270DAFF900D27C52 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; 55652F68438D6EDFE71EA13C /* Pods-FlowCryptUIApplication.enterprise.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUIApplication.enterprise.xcconfig"; path = "Target Support Files/Pods-FlowCryptUIApplication/Pods-FlowCryptUIApplication.enterprise.xcconfig"; sourceTree = ""; }; 5A39F42C239EC321001F4607 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; @@ -729,7 +731,7 @@ D2E26F6724F169E300612AF1 /* ContactCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCellNode.swift; sourceTree = ""; }; D2E26F6B24F25B1F00612AF1 /* KeyAlgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyAlgo.swift; sourceTree = ""; }; D2E26F6F24F266F300612AF1 /* ContactDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailViewController.swift; sourceTree = ""; }; - D2E26F7124F26FFF00612AF1 /* ContactDetailNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailNode.swift; sourceTree = ""; }; + D2E26F7124F26FFF00612AF1 /* ContactUserCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUserCellNode.swift; sourceTree = ""; }; D2E26F7324F2705B00612AF1 /* ContactDetailDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailDecorator.swift; sourceTree = ""; }; D2F18463244B0C63000CC5D1 /* SignInGoogleTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInGoogleTest.swift; sourceTree = ""; }; D2F18467244B0F35000CC5D1 /* AppTestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTestHelper.swift; sourceTree = ""; }; @@ -1911,7 +1913,8 @@ 9FDF3637235A0B3100614596 /* InfoCellNode.swift */, D2F6D12E24324ACC00DB4065 /* SwitchCellNode.swift */, D2E26F6724F169E300612AF1 /* ContactCellNode.swift */, - D2E26F7124F26FFF00612AF1 /* ContactDetailNode.swift */, + D2E26F7124F26FFF00612AF1 /* ContactUserCellNode.swift */, + 51DE2FED2714DA0400916222 /* ContactKeyCellNode.swift */, D20D3C7B2520ABC600D4AA9A /* BackupCellNode.swift */, D28A1CBC2525C141003B760B /* CheckBoxTextNode.swift */, ); @@ -2681,6 +2684,7 @@ D211CE7123FC35AC00D1CE38 /* TextFieldCellNode.swift in Sources */, 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */, D2CCD1FA247C50DA00D21F9C /* UIColorExtension.swift in Sources */, + 51DE2FEE2714DA0400916222 /* ContactKeyCellNode.swift in Sources */, D2A9CA432426210200E1D898 /* SetupTitleNode.swift in Sources */, D2F6D12F24324ACC00DB4065 /* SwitchCellNode.swift in Sources */, D2E26F6824F169E300612AF1 /* ContactCellNode.swift in Sources */, @@ -2689,7 +2693,7 @@ D2CDC3D72404704D002B045F /* RecipientEmailsCellNode.swift in Sources */, D2717752242567EB00BDA9A9 /* KeyTextCellNode.swift in Sources */, D211CE7B23FC59ED00D1CE38 /* InfoCellNode.swift in Sources */, - D2E26F7224F26FFF00612AF1 /* ContactDetailNode.swift in Sources */, + D2E26F7224F26FFF00612AF1 /* ContactUserCellNode.swift in Sources */, D211CE6F23FC358000D1CE38 /* ButtonNode.swift in Sources */, D28A1CBD2525C141003B760B /* CheckBoxTextNode.swift in Sources */, D2A9CA3B242619A400E1D898 /* SignInImageNode.swift in Sources */, diff --git a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved index 816067881..44d4ba74b 100644 --- a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -46,6 +46,15 @@ "version": "1.7.0" } }, + { + "package": "GTMAppAuth", + "repositoryURL": "https://github.com/google/GTMAppAuth.git", + "state": { + "branch": null, + "revision": "40f4103fb52109032c05599a0c39ad43edbdf80a", + "version": "1.2.2" + } + }, { "package": "IDZSwiftCommonCrypto", "repositoryURL": "https://github.com/iosdevzone/IDZSwiftCommonCrypto", diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift index c449ebaff..3f896f70d 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift @@ -11,30 +11,24 @@ import Foundation protocol ContactDetailDecoratorType { var title: String { get } - func nodeInput(with contact: Contact) -> ContactDetailNode.Input + func userNodeInput(with contact: Contact) -> ContactUserCellNode.Input + func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input } struct ContactDetailDecorator: ContactDetailDecoratorType { let title = "contact_detail_screen_title".localized - func nodeInput(with contact: Contact) -> ContactDetailNode.Input { - let createdString: String = { - if let created = contact.pubkeyCreated { - let df = DateFormatter() - df.dateStyle = .medium - df.timeStyle = .medium - return df.string(from: created) - } else { - return "-" - } + func userNodeInput(with contact: Contact) -> ContactUserCellNode.Input { + ContactUserCellNode.Input( + user: (contact.name ?? contact.email).attributed(.regular(16)) + ) + } - }() - return ContactDetailNode.Input( - user: (contact.name ?? contact.email).attributed(.regular(16)), - ids: contact.longids.joined(separator: ",\n").attributed(.regular(14)), - fingerprints: contact.fingerprints.joined(separator: ",\n").attributed(.regular(14)), - algoInfo: contact.algo?.algorithm.attributed(.regular(14)), - created: createdString.attributed(.regular(14)) + func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input { + ContactKeyCellNode.Input( + fingerprint: key.fingerprint?.attributed(.regular(12)), + createdAt: "".attributed(.regular(14)), + expires: "".attributed(.regular(14)) ) } } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift index ce67c1c7f..97038e824 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift @@ -20,6 +20,10 @@ final class ContactDetailViewController: TableNodeViewController { case delete(_ contact: Contact) } + private enum Section: Int, CaseIterable { + case header = 0, keys + } + private let decorator: ContactDetailDecoratorType private let contact: Contact private let action: ContactDetailAction? @@ -51,28 +55,13 @@ final class ContactDetailViewController: TableNodeViewController { private func setupNavigationBarItems() { navigationItem.rightBarButtonItem = NavigationBarItemsView( with: [ - .init(image: UIImage(named: "share"), action: (self, #selector(handleSaveAction))), - .init(image: UIImage(named: "copy"), action: (self, #selector(handleCopyAction))), - .init(image: UIImage(named: "trash"), action: (self, #selector(handleRemoveAction))) + .init(image: UIImage(systemName: "trash"), action: (self, #selector(handleRemoveAction))) ] ) } } extension ContactDetailViewController { - @objc private final func handleSaveAction() { - let vc = UIActivityViewController( - activityItems: [contact.pubKey], - applicationActivities: nil - ) - present(vc, animated: true, completion: nil) - } - - @objc private final func handleCopyAction() { - UIPasteboard.general.string = contact.pubKey - showToast("contact_detail_copy".localized) - } - @objc private final func handleRemoveAction() { navigationController?.popViewController(animated: true) { [weak self] in guard let self = self else { return } @@ -82,14 +71,38 @@ extension ContactDetailViewController { } extension ContactDetailViewController: ASTableDelegate, ASTableDataSource { - func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { - 1 + func numberOfSections(in tableNode: ASTableNode) -> Int { + Section.allCases.count + } + + func tableNode(_: ASTableNode, numberOfRowsInSection section: Int) -> Int { + guard let section = Section(rawValue: section) else { return 0 } + + switch section { + case .header: return 1 + case .keys: return contact.pubKeys.count + } } func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { { [weak self] in - guard let self = self else { return ASCellNode() } - return ContactDetailNode(input: self.decorator.nodeInput(with: self.contact)) + guard let self = self, let section = Section(rawValue: indexPath.section) + else { return ASCellNode() } + return self.node(for: section, row: indexPath.row) + } + } +} + +// MARK: - UI +extension ContactDetailViewController { + private func node(for section: Section, row: Int) -> ASCellNode { + switch section { + case .header: + return ContactUserCellNode(input: self.decorator.userNodeInput(with: self.contact)) + case .keys: + return ContactKeyCellNode( + input: self.decorator.keyNodeInput(with: self.contact.pubKeys[row]) + ) } } } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift index 2eff9d7c8..e9a49660a 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift @@ -39,9 +39,12 @@ struct ContactsListDecorator: ContactsListDecoratorType { lightStyle: .darkGray ) + let keysCount = "%@ public key(s)".localizeWithArguments(contact.pubKeys.count) + return ContactCellNode.Input( name: name.attributed(.medium(16)), email: contact.email.attributed(.medium(14)), + keys: "(\(keysCount))".attributed(.medium(14), color: .textColor.withAlphaComponent(0.5)), insets: UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16), buttonImage: #imageLiteral(resourceName: "trash").tinted(buttonColor) ) diff --git a/FlowCrypt/Models/Realm Models/ContactKeyObject.swift b/FlowCrypt/Models/Realm Models/ContactKeyObject.swift index 5d27c50c8..39a93ecb7 100644 --- a/FlowCrypt/Models/Realm Models/ContactKeyObject.swift +++ b/FlowCrypt/Models/Realm Models/ContactKeyObject.swift @@ -10,7 +10,7 @@ import Foundation import RealmSwift final class ContactKeyObject: Object { - @Persisted var key: String = "" + @Persisted(primaryKey: true) var key: String = "" @Persisted var lastSig: Date? @Persisted var lastChecked: Date? @@ -18,4 +18,37 @@ final class ContactKeyObject: Object { @Persisted var longids: List @Persisted var fingerprints: List @Persisted var created: Date? + + convenience init(key: String, + lastSig: Date? = nil, + lastChecked: Date? = nil, + expiresOn: Date? = nil, + longids: [String] = [], + fingerprints: [String] = [], + created: Date? = nil) { + self.init() + + self.key = key + self.lastSig = lastSig + self.lastChecked = lastChecked + self.expiresOn = expiresOn + self.created = created + + longids.forEach { self.longids.append($0) } + fingerprints.forEach { self.fingerprints.append($0) } + } +} + +extension ContactKeyObject { + convenience init(_ key: ContactKey) { + self.init( + key: key.key, + lastSig: key.lastSig, + lastChecked: key.lastChecked, + expiresOn: key.expiresOn, + longids: key.longids, + fingerprints: key.fingerprints, + created: key.created + ) + } } diff --git a/FlowCrypt/Resources/Localizable.stringsdict b/FlowCrypt/Resources/Localizable.stringsdict index e033f1e37..a639e660d 100644 --- a/FlowCrypt/Resources/Localizable.stringsdict +++ b/FlowCrypt/Resources/Localizable.stringsdict @@ -20,5 +20,23 @@ Fetched %d keys on EKM + %@ public key(s) + + NSStringLocalizedFormatKey + %#@keys@ + keys + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + No public keys + one + %d public key + other + %d public keys + + diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 8fdd4d298..0d65c17be 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -141,7 +141,7 @@ // Contacts "contacts_screen_title" = "Contacts"; -"contact_detail_screen_title" = "Public Key"; +"contact_detail_screen_title" = "Public Keys"; "contact_detail_copy" = "Public Key copied to clipboard"; // Key Settings diff --git a/FlowCryptUI/Cell Nodes/ContactCellNode.swift b/FlowCryptUI/Cell Nodes/ContactCellNode.swift index 45d1bf555..37858f502 100644 --- a/FlowCryptUI/Cell Nodes/ContactCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ContactCellNode.swift @@ -12,17 +12,20 @@ public final class ContactCellNode: CellNode { public struct Input { let name: NSAttributedString? let email: NSAttributedString + let keys: NSAttributedString let insets: UIEdgeInsets let buttonImage: UIImage? public init( name: NSAttributedString?, email: NSAttributedString, + keys: NSAttributedString, insets: UIEdgeInsets, buttonImage: UIImage? ) { self.name = name self.email = email + self.keys = keys self.insets = insets self.buttonImage = buttonImage } @@ -30,6 +33,7 @@ public final class ContactCellNode: CellNode { private let nameNode = ASTextNode2() private let emailNode = ASTextNode2() + private let keysNode = ASTextNode2() private let buttonNode = ASButtonNode() private let input: Input @@ -42,6 +46,7 @@ public final class ContactCellNode: CellNode { nameNode.attributedText = input.name emailNode.attributedText = input.email + keysNode.attributedText = input.keys buttonNode.setImage(input.buttonImage, for: .normal) buttonNode.addTarget(self, action: #selector(handleButtonTap), forControlEvents: .touchUpInside) } @@ -56,12 +61,19 @@ public final class ContactCellNode: CellNode { emailNode.style.flexGrow = 1 children = [emailNode, buttonNode] } else { + let infoStack = ASStackLayoutSpec( + direction: .horizontal, + spacing: 4, + justifyContent: .start, + alignItems: .start, + children: [emailNode, keysNode] + ) let nameStack = ASStackLayoutSpec( direction: .vertical, spacing: 8, justifyContent: .start, alignItems: .start, - children: [nameNode, emailNode] + children: [nameNode, infoStack] ) nameStack.style.flexGrow = 1 children = [nameStack, buttonNode] diff --git a/FlowCryptUI/Cell Nodes/ContactDetailNode.swift b/FlowCryptUI/Cell Nodes/ContactDetailNode.swift deleted file mode 100644 index 670c1716b..000000000 --- a/FlowCryptUI/Cell Nodes/ContactDetailNode.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// ContactDetailNode.swift -// FlowCryptUI -// -// Created by Anton Kharchevskyi on 23/08/2020. -// Copyright © 2017-present FlowCrypt a. s. All rights reserved. -// - -import AsyncDisplayKit - -public final class ContactDetailNode: CellNode { - public struct Input { - let user: NSAttributedString - let ids: NSAttributedString - let fingerprints: NSAttributedString - let algoInfo: NSAttributedString? - let created: NSAttributedString? - - public init( - user: NSAttributedString, - ids: NSAttributedString, - fingerprints: NSAttributedString, - algoInfo: NSAttributedString?, - created: NSAttributedString? - ) { - self.user = user - self.ids = ids - self.fingerprints = fingerprints - self.algoInfo = algoInfo - self.created = created - } - } - - private let input: Input - - private let userTitleNode = ASTextNode2() - private let userNode = ASTextNode2() - - private let idsTitleNode = ASTextNode2() - private let idsNode = ASTextNode2() - - private let fingerprintsTitleNode = ASTextNode2() - private let fingerprintsNode = ASTextNode2() - - private let algoTitleNode = ASTextNode2() - private let algoNode = ASTextNode2() - - private let createdTitleNode = ASTextNode2() - private let createdNode = ASTextNode2() - - public init(input: Input) { - self.input = input - userTitleNode.attributedText = "User:".attributed(.bold(16)) - userNode.attributedText = input.user - - idsTitleNode.attributedText = "Longids:".attributed(.bold(16)) - idsNode.attributedText = input.ids - - fingerprintsTitleNode.attributedText = "Fingerprints:".attributed(.bold(16)) - fingerprintsNode.attributedText = input.fingerprints - - algoTitleNode.attributedText = "Algorithm:".attributed(.bold(16)) - algoNode.attributedText = input.algoInfo - - createdTitleNode.attributedText = "Created:".attributed(.bold(16)) - createdNode.attributedText = input.created - } - - public override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { - let specs = [ - [userTitleNode, userNode], - [idsTitleNode, idsNode], - [fingerprintsTitleNode, fingerprintsNode], - [algoTitleNode, algoNode], - [createdTitleNode, createdNode] - ] - .map { - ASStackLayoutSpec( - direction: .vertical, - spacing: 4, - justifyContent: .start, - alignItems: .start, - children: $0 - ) - } - - return ASInsetLayoutSpec( - insets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), - child: ASStackLayoutSpec( - direction: .vertical, - spacing: 12, - justifyContent: .start, - alignItems: .start, - children: specs - ) - ) - } -} diff --git a/FlowCryptUI/Cell Nodes/ContactKeyCellNode.swift b/FlowCryptUI/Cell Nodes/ContactKeyCellNode.swift new file mode 100644 index 000000000..af6c9cb42 --- /dev/null +++ b/FlowCryptUI/Cell Nodes/ContactKeyCellNode.swift @@ -0,0 +1,58 @@ +// +// ContactKeyCellNode.swift +// FlowCryptUI +// +// Created by Roma Sosnovsky on 11/10/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + + +import AsyncDisplayKit +import Foundation + +public final class ContactKeyCellNode: CellNode { + public struct Input { + let fingerprint: NSAttributedString? + let createdAt: NSAttributedString? + let expires: NSAttributedString + + public init( + fingerprint: NSAttributedString?, + createdAt: NSAttributedString?, + expires: NSAttributedString + ) { + self.fingerprint = fingerprint + self.createdAt = createdAt + self.expires = expires + } + } + + private let fingerprintNode = ASTextNode2() + private let createdAtNode = ASTextNode2() + private let expiresNode = ASTextNode2() + + private let input: Input + + public init(input: Input) { + self.input = input + + super.init() + + fingerprintNode.attributedText = input.fingerprint + createdAtNode.attributedText = input.createdAt + expiresNode.attributedText = input.expires + } + + public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { + ASInsetLayoutSpec( + insets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), + child: ASStackLayoutSpec( + direction: .vertical, + spacing: 8, + justifyContent: .start, + alignItems: .start, + children: [fingerprintNode, createdAtNode, expiresNode] + ) + ) + } +} diff --git a/FlowCryptUI/Cell Nodes/ContactUserCellNode.swift b/FlowCryptUI/Cell Nodes/ContactUserCellNode.swift new file mode 100644 index 000000000..21dd2aa0a --- /dev/null +++ b/FlowCryptUI/Cell Nodes/ContactUserCellNode.swift @@ -0,0 +1,43 @@ +// +// ContactUserCellNode.swift +// FlowCryptUI +// +// Created by Anton Kharchevskyi on 23/08/2020. +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import AsyncDisplayKit + +public final class ContactUserCellNode: CellNode { + public struct Input { + let user: NSAttributedString + + public init(user: NSAttributedString) { + self.user = user + } + } + + private let input: Input + + private let userTitleNode = ASTextNode2() + private let userNode = ASTextNode2() + + public init(input: Input) { + self.input = input + userTitleNode.attributedText = "User:".attributed(.bold(16)) + userNode.attributedText = input.user + } + + public override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + ASInsetLayoutSpec( + insets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), + child: ASStackLayoutSpec( + direction: .vertical, + spacing: 4, + justifyContent: .start, + alignItems: .start, + children: [userTitleNode, userNode] + ) + ) + } +} diff --git a/Podfile.lock b/Podfile.lock index d62c86b89..6cc97ac61 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.48.14) + - SwiftFormat/CLI (0.48.15) - SwiftLint (0.44.0) - SwiftyRSA (1.7.0): - SwiftyRSA/ObjC (= 1.7.0) @@ -67,7 +67,7 @@ SPEC CHECKSUMS: PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01 - SwiftFormat: 45eb1675b89bee034a18693dfc550bd5c98d1b2d + SwiftFormat: ff32cb3c0c10c3c8805d57aa0bc25de306fd0521 SwiftLint: e96c0a8c770c7ebbc4d36c55baf9096bb65c4584 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3 From 5b0327e67c88971c4d91bc661eb88ea7dbbf405b Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 13 Oct 2021 00:25:42 +0300 Subject: [PATCH 03/10] issue #553 show all contact keys --- .../ContactDetailDecorator.swift | 24 +++++-- .../ContactDetailViewController.swift | 11 ++++ .../Contacts List/ContactsListDecorator.swift | 4 +- .../Cell Nodes/ContactKeyCellNode.swift | 66 ++++++++++++++++--- 4 files changed, 90 insertions(+), 15 deletions(-) diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift index 3f896f70d..cb0a02ce3 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift @@ -25,10 +25,26 @@ struct ContactDetailDecorator: ContactDetailDecoratorType { } func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input { - ContactKeyCellNode.Input( - fingerprint: key.fingerprint?.attributed(.regular(12)), - createdAt: "".attributed(.regular(14)), - expires: "".attributed(.regular(14)) + let df = DateFormatter() + df.dateStyle = .medium + df.timeStyle = .medium + + let fingerpringString = key.fingerprint ?? "-" + + let createdString: String = { + guard let created = key.created else { return "-" } + return df.string(from: created) + }() + + let expiresString: String = { + guard let expires = key.expiresOn else { return "never" } + return df.string(from: expires) + }() + + return ContactKeyCellNode.Input( + fingerprint: fingerpringString.attributed(.regular(13)), + createdAt: createdString.attributed(.regular(14)), + expires: expiresString.attributed(.regular(14)) ) } } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift index 97038e824..8e0565c7e 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift @@ -91,6 +91,17 @@ extension ContactDetailViewController: ASTableDelegate, ASTableDataSource { return self.node(for: section, row: indexPath.row) } } + + func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { + guard let section = Section(rawValue: indexPath.section) else { return } + + switch section { + case .header: + return + case .keys: + return + } + } } // MARK: - UI diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift index e9a49660a..0dd0a8270 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift @@ -44,9 +44,9 @@ struct ContactsListDecorator: ContactsListDecoratorType { return ContactCellNode.Input( name: name.attributed(.medium(16)), email: contact.email.attributed(.medium(14)), - keys: "(\(keysCount))".attributed(.medium(14), color: .textColor.withAlphaComponent(0.5)), + keys: "(\(keysCount))".attributed(.medium(14), color: .mainTextColor.withAlphaComponent(0.5)), insets: UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16), - buttonImage: #imageLiteral(resourceName: "trash").tinted(buttonColor) + buttonImage: UIImage(systemName: "trash")?.tinted(buttonColor) ) } diff --git a/FlowCryptUI/Cell Nodes/ContactKeyCellNode.swift b/FlowCryptUI/Cell Nodes/ContactKeyCellNode.swift index af6c9cb42..8e546a7b5 100644 --- a/FlowCryptUI/Cell Nodes/ContactKeyCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ContactKeyCellNode.swift @@ -12,13 +12,13 @@ import Foundation public final class ContactKeyCellNode: CellNode { public struct Input { - let fingerprint: NSAttributedString? - let createdAt: NSAttributedString? + let fingerprint: NSAttributedString + let createdAt: NSAttributedString let expires: NSAttributedString public init( - fingerprint: NSAttributedString?, - createdAt: NSAttributedString?, + fingerprint: NSAttributedString, + createdAt: NSAttributedString, expires: NSAttributedString ) { self.fingerprint = fingerprint @@ -27,10 +27,17 @@ public final class ContactKeyCellNode: CellNode { } } + private let fingerprintTitleNode = ASTextNode2() private let fingerprintNode = ASTextNode2() + + private let createdAtTitleNode = ASTextNode2() private let createdAtNode = ASTextNode2() + + private let expiresTitleNode = ASTextNode2() private let expiresNode = ASTextNode2() + private let borderNode = ASDisplayNode() + private let input: Input public init(input: Input) { @@ -38,20 +45,61 @@ public final class ContactKeyCellNode: CellNode { super.init() + fingerprintTitleNode.attributedText = "Fingerprint:".attributed(.bold(16)) fingerprintNode.attributedText = input.fingerprint + + createdAtTitleNode.attributedText = "Created:".attributed(.bold(16)) createdAtNode.attributedText = input.createdAt + + expiresTitleNode.attributedText = "Expires:".attributed(.bold(16)) expiresNode.attributedText = input.expires + + borderNode.borderWidth = 1.0 + borderNode.cornerRadius = 8.0 + borderNode.borderColor = UIColor.lightGray.cgColor + borderNode.isUserInteractionEnabled = false } public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { - ASInsetLayoutSpec( - insets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), - child: ASStackLayoutSpec( + let specs = [ + [fingerprintTitleNode, fingerprintNode], + [createdAtTitleNode, createdAtNode], + [expiresTitleNode, expiresNode] + ].map { + ASStackLayoutSpec( direction: .vertical, - spacing: 8, + spacing: 4, justifyContent: .start, alignItems: .start, - children: [fingerprintNode, createdAtNode, expiresNode] + children: $0 + ) + } + + let stack = ASStackLayoutSpec( + direction: .vertical, + spacing: 8, + justifyContent: .start, + alignItems: .start, + children: specs + ) + + let borderInset = UIEdgeInsets.side(8) + + let resultSpec = ASInsetLayoutSpec( + insets: UIEdgeInsets( + top: 10 + borderInset.top, + left: 12 + borderInset.left, + bottom: 10 + borderInset.bottom, + right: 12 + borderInset.right + ), + child: stack + ) + + return ASOverlayLayoutSpec( + child: resultSpec, + overlay: ASInsetLayoutSpec( + insets: borderInset, + child: borderNode ) ) } From 4ea1cd38fa31cd21de521479c08215c5c904bb5a Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 13 Oct 2021 14:07:01 +0300 Subject: [PATCH 04/10] issue #553 add contact key detail screen --- FlowCrypt.xcodeproj/project.pbxproj | 12 ++ .../ContactDetailViewController.swift | 34 +++- .../ContactKeyDetailDecorator.swift | 50 ++++++ .../ContactKeyDetailViewController.swift | 155 ++++++++++++++++++ .../ContactsListViewController.swift | 4 +- .../Contacts Services/ContactsService.swift | 5 + .../LocalContactsProvider.swift | 17 +- .../Contacts Services/Models/ContactKey.swift | 6 + FlowCryptUI/Cell Nodes/LabelCellNode.swift | 37 +++++ 9 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailDecorator.swift create mode 100644 FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift create mode 100644 FlowCryptUI/Cell Nodes/LabelCellNode.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index fa3731188..e30500c7f 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -54,6 +54,9 @@ 50531BE42629B9A80039BAE9 /* AttachmentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50531BE32629B9A80039BAE9 /* AttachmentNode.swift */; }; 510A260126FDFEBE00163271 /* MailCore2 in Frameworks */ = {isa = PBXBuildFile; productRef = 510A260026FDFEBE00163271 /* MailCore2 */; }; 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */ = {isa = PBXBuildFile; productRef = 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */; }; + 5133B6702716320F00C95463 /* ContactKeyDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */; }; + 5133B6722716321F00C95463 /* ContactKeyDetailDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B6712716321F00C95463 /* ContactKeyDetailDecorator.swift */; }; + 5133B6742716E5EA00C95463 /* LabelCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B6732716E5EA00C95463 /* LabelCellNode.swift */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; 51B4AE4F27137CB70001F33B /* Promises in Frameworks */ = {isa = PBXBuildFile; productRef = 51B4AE4E27137CB70001F33B /* Promises */; }; @@ -466,6 +469,9 @@ 4C5032E4FC5685A224F61785 /* Pods-FlowCryptUI.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUI.testflight.xcconfig"; path = "Target Support Files/Pods-FlowCryptUI/Pods-FlowCryptUI.testflight.xcconfig"; sourceTree = ""; }; 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 = ""; }; + 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailViewController.swift; sourceTree = ""; }; + 5133B6712716321F00C95463 /* ContactKeyDetailDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailDecorator.swift; sourceTree = ""; }; + 5133B6732716E5EA00C95463 /* LabelCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCellNode.swift; sourceTree = ""; }; 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrvKeyInfoTests.swift; sourceTree = ""; }; 51775C38270C7D2400D7C944 /* StorageMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageMethod.swift; sourceTree = ""; }; 51B4AE50271444580001F33B /* ContactKeyObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyObject.swift; sourceTree = ""; }; @@ -1917,6 +1923,7 @@ 51DE2FED2714DA0400916222 /* ContactKeyCellNode.swift */, D20D3C7B2520ABC600D4AA9A /* BackupCellNode.swift */, D28A1CBC2525C141003B760B /* CheckBoxTextNode.swift */, + 5133B6732716E5EA00C95463 /* LabelCellNode.swift */, ); path = "Cell Nodes"; sourceTree = ""; @@ -1957,6 +1964,8 @@ D2E26F6524F169B400612AF1 /* ContactsListDecorator.swift */, D2E26F6F24F266F300612AF1 /* ContactDetailViewController.swift */, D2E26F7324F2705B00612AF1 /* ContactDetailDecorator.swift */, + 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */, + 5133B6712716321F00C95463 /* ContactKeyDetailDecorator.swift */, ); path = "Contacts List"; sourceTree = ""; @@ -2549,6 +2558,7 @@ 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */, 5ADEDCB923A42B9400EC495E /* KeyDetailViewDecorator.swift in Sources */, 9F416428266575DC00106194 /* BackupServiceType.swift in Sources */, + 5133B6702716320F00C95463 /* ContactKeyDetailViewController.swift in Sources */, 9F7E5137267AA51B00CE37C3 /* AlertsFactory.swift in Sources */, 5A39F437239ECC23001F4607 /* KeySettingsViewController.swift in Sources */, 9FF0673325520DE400FCC9E6 /* GmailService+send.swift in Sources */, @@ -2585,6 +2595,7 @@ 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */, 9FC4112E2595EA8B001180A8 /* Gmail+Search.swift in Sources */, 5A948DC5239EF2F4006284D7 /* LegalViewController.swift in Sources */, + 5133B6722716321F00C95463 /* ContactKeyDetailDecorator.swift in Sources */, D274724124F97C5C006BA6EF /* CacheService.swift in Sources */, A3B7C31923F576BA0022D628 /* AppStartup.swift in Sources */, 9F31AB8E23298BCF00CF87EA /* Imap+folders.swift in Sources */, @@ -2681,6 +2692,7 @@ D271774A242558DA00BDA9A9 /* MessageSenderNode.swift in Sources */, D2717754242568A600BDA9A9 /* NavigationBarActionButton.swift in Sources */, D2A9CA3A2426198600E1D898 /* SignInDescriptionNode.swift in Sources */, + 5133B6742716E5EA00C95463 /* LabelCellNode.swift in Sources */, D211CE7123FC35AC00D1CE38 /* TextFieldCellNode.swift in Sources */, 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */, D2CCD1FA247C50DA00D21F9C /* UIColorExtension.swift in Sources */, diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift index 8e0565c7e..4eb9dcba9 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift @@ -7,6 +7,7 @@ // import AsyncDisplayKit +import FlowCryptCommon import FlowCryptUI /** @@ -68,6 +69,28 @@ extension ContactDetailViewController { self.action?(.delete(self.contact)) } } + + private func delete(with context: Either) { + let keyToRemove: ContactKey + let indexPathToRemove: IndexPath + switch context { + case .left(let key): + keyToRemove = key + guard let index = contact.pubKeys.firstIndex(where: { $0 == key }) else { + assertionFailure("Can't find index of the contact") + return + } + indexPathToRemove = IndexPath(row: index, section: 0) + case .right(let indexPath): + indexPathToRemove = indexPath + keyToRemove = contact.pubKeys[indexPath.row] + } + + // TODO: Contact provider + // contact.pubKeys.remove(at: indexPathToRemove.row) + + node.deleteRows(at: [indexPathToRemove], with: .left) + } } extension ContactDetailViewController: ASTableDelegate, ASTableDataSource { @@ -99,7 +122,16 @@ extension ContactDetailViewController: ASTableDelegate, ASTableDataSource { case .header: return case .keys: - return + let key = contact.pubKeys[indexPath.row] + let contactKeyDetailViewController = ContactKeyDetailViewController(key: key) { [weak self] action in + guard case let .delete(key) = action else { + assertionFailure("Action is not implemented") + return + } + self?.delete(with: .left(key)) + } + + navigationController?.pushViewController(contactKeyDetailViewController, animated: true) } } } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailDecorator.swift new file mode 100644 index 000000000..0249e4a25 --- /dev/null +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailDecorator.swift @@ -0,0 +1,50 @@ +// +// ContactKeyDetailDecorator.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 13/10/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import FlowCryptUI +import Foundation + +protocol ContactKeyDetailDecoratorType { + var title: String { get } + func userNodeInput(with contact: Contact) -> ContactUserCellNode.Input + func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input +} + +struct ContactKeyDetailDecorator: ContactKeyDetailDecoratorType { + let title = "contact_detail_screen_title".localized + + func userNodeInput(with contact: Contact) -> ContactUserCellNode.Input { + ContactUserCellNode.Input( + user: (contact.name ?? contact.email).attributed(.regular(16)) + ) + } + + func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input { + let df = DateFormatter() + df.dateStyle = .medium + df.timeStyle = .medium + + let fingerpringString = key.fingerprint ?? "-" + + let createdString: String = { + guard let created = key.created else { return "-" } + return df.string(from: created) + }() + + let expiresString: String = { + guard let expires = key.expiresOn else { return "never" } + return df.string(from: expires) + }() + + return ContactKeyCellNode.Input( + fingerprint: fingerpringString.attributed(.regular(13)), + createdAt: createdString.attributed(.regular(14)), + expires: expiresString.attributed(.regular(14)) + ) + } +} diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift new file mode 100644 index 000000000..e024d818e --- /dev/null +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift @@ -0,0 +1,155 @@ +// +// ContactKeyDetailViewController.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 13/10/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import AsyncDisplayKit +import FlowCryptCommon +import FlowCryptUI + +/** + * View controller which shows details about a contact and the public key recorded for it + * - User can be redirected here from settings *ContactsListViewController* by tapping on a particular contact + */ +final class ContactKeyDetailViewController: TableNodeViewController { + typealias ContactKeyDetailAction = (Action) -> Void + + enum Action { + case delete(_ key: ContactKey) + } + + private enum Section: Int, CaseIterable { + case key = 0, signature, checked, expire, longids, fingerprints, created, algo + } + + private let decorator: ContactKeyDetailDecoratorType + private let key: ContactKey + private let action: ContactKeyDetailAction? + + init( + decorator: ContactKeyDetailDecoratorType = ContactKeyDetailDecorator(), + key: ContactKey, + action: ContactKeyDetailAction? + ) { + self.decorator = decorator + self.key = key + self.action = action + super.init(node: TableNode()) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + node.delegate = self + node.dataSource = self + title = decorator.title + setupNavigationBarItems() + } + + private func setupNavigationBarItems() { + navigationItem.rightBarButtonItem = NavigationBarItemsView( + with: [ + .init(image: UIImage(named: "share"), action: (self, #selector(handleSaveAction))), + .init(image: UIImage(named: "copy"), action: (self, #selector(handleCopyAction))), + .init(image: UIImage(named: "trash"), action: (self, #selector(handleRemoveAction))) + ] + ) + } +} + +extension ContactKeyDetailViewController { + @objc private final func handleSaveAction() { + let vc = UIActivityViewController( + activityItems: [key.key], + applicationActivities: nil + ) + present(vc, animated: true, completion: nil) + } + + @objc private final func handleCopyAction() { + UIPasteboard.general.string = key.key + showToast("contact_detail_copy".localized) + } + + @objc private final func handleRemoveAction() { + navigationController?.popViewController(animated: true) { [weak self] in + guard let self = self else { return } + self.action?(.delete(self.key)) + } + } +} + +extension ContactKeyDetailViewController: ASTableDelegate, ASTableDataSource { + func numberOfSections(in tableNode: ASTableNode) -> Int { + Section.allCases.count + } + + func tableNode(_: ASTableNode, numberOfRowsInSection section: Int) -> Int { + 1 + } + + func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { + { [weak self] in + guard let self = self, let section = Section(rawValue: indexPath.section) + else { return ASCellNode() } + return self.node(for: section, row: indexPath.row) + } + } +} + +// MARK: - UI +extension ContactKeyDetailViewController { + private func node(for section: Section, row: Int) -> ASCellNode { + let df = DateFormatter() + df.dateStyle = .medium + df.timeStyle = .medium + + switch section { + case .key: + return LabelCellNode(title: "Key".attributed(.bold(16)), + text: key.key.attributed(.regular(14))) + case .signature: + return LabelCellNode(title: "Signature".attributed(.bold(16)), + text: "Text".attributed(.regular(14))) + case .checked: + let checkedString: String = { + guard let created = key.lastChecked else { return "-" } + return df.string(from: created) + }() + return LabelCellNode(title: "Last fetched".attributed(.bold(16)), + text: checkedString.attributed(.regular(14))) + case .expire: + let expireString: String = { + guard let expires = key.expiresOn else { return "-" } + return df.string(from: expires) + }() + return LabelCellNode(title: "Expires".attributed(.bold(16)), + text: expireString.attributed(.regular(14))) + case .longids: + return LabelCellNode(title: "Longids".attributed(.bold(16)), + text: key.longids.joined(separator: ", ").attributed(.regular(14))) + case .fingerprints: + return LabelCellNode(title: "Fingerprints".attributed(.bold(16)), + text: key.fingerprints.joined(separator: ", ").attributed(.regular(14))) + case .created: + let createdString: String = { + guard let created = key.created else { return "-" } + return df.string(from: created) + }() + return LabelCellNode(title: "Created".attributed(.bold(16)), + text: createdString.attributed(.regular(14))) + case .algo: + let algoString = key.algo?.algorithm ?? "-" + return LabelCellNode(title: "Algo".attributed(.bold(16)), + text: algoString.attributed(.regular(14))) + } + } + +} diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift index a8b64a71d..0140bfa1b 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift @@ -73,7 +73,7 @@ extension ContactsListViewController: ASTableDelegate, ASTableDataSource { } func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { - proceedToKeyDetail(with: indexPath) + proceedToContactDetail(with: indexPath) } } @@ -82,7 +82,7 @@ extension ContactsListViewController { delete(with: .right(indexPath)) } - private func proceedToKeyDetail(with indexPath: IndexPath) { + private func proceedToContactDetail(with indexPath: IndexPath) { let contactDetailViewController = ContactDetailViewController( contact: contacts[indexPath.row] ) { [weak self] action in diff --git a/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift index 32ba57354..446ec72ca 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift @@ -23,6 +23,7 @@ protocol ContactsProviderType { protocol PublicKeyProvider { func retrievePubKeys(for email: String) -> [String] + func remove(pubKey: ContactKey, for email: String) } // MARK: - PROVIDER @@ -63,4 +64,8 @@ extension ContactsService: PublicKeyProvider { localContactsProvider.updateLastUsedDate(for: email) return publicKeys } + + func remove(pubKey: ContactKey, for email: String) { + //let keys = retrievePubKeys(for: email).filter { $0 != pubKey } + } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift index db6feb9af..c99473be2 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift @@ -72,12 +72,25 @@ extension LocalContactsProvider: LocalContactsProviderType { localContactsCache.realm .objects(ContactObject.self) .map { object in - let keyDetails = object - .pubKeys + let keyDetails = object.pubKeys .compactMap { try? core.parseKeys(armoredOrBinary: $0.key.data()).keyDetails } .flatMap { $0 } return Contact(object, keyDetails: Array(keyDetails)) } .sorted(by: { $0.email > $1.email }) } + + func remove(pubKey: ContactKey, for email: String) { + guard let contact = searchContact(with: email) else { + // TODO: Handle + return + } + + let updatedContact = Contact(email: contact.email, + name: contact.name, + pubKeys: contact.pubKeys.filter { $0 != pubKey }, + lastUsed: contact.lastUsed) + + save(contact: updatedContact) + } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift b/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift index 9e11172cc..79ea3d8ef 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift @@ -49,3 +49,9 @@ extension ContactKey { algo: keyDetails.algo) } } + +extension ContactKey: Equatable { + static func == (lhs: ContactKey, rhs: ContactKey) -> Bool { + lhs.key == rhs.key + } +} diff --git a/FlowCryptUI/Cell Nodes/LabelCellNode.swift b/FlowCryptUI/Cell Nodes/LabelCellNode.swift new file mode 100644 index 000000000..5df464451 --- /dev/null +++ b/FlowCryptUI/Cell Nodes/LabelCellNode.swift @@ -0,0 +1,37 @@ +// +// LabelCellNode.swift +// FlowCryptUI +// +// Created by Roma Sosnovsky on 13/10/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import AsyncDisplayKit + +public final class LabelCellNode: CellNode { + private let titleNode = ASTextNode2() + private let textNode = ASTextNode2() + private let insets: UIEdgeInsets + + public init(title: NSAttributedString, + text: NSAttributedString, + insets: UIEdgeInsets = UIEdgeInsets.side(8)) { + self.insets = insets + super.init() + titleNode.attributedText = title + textNode.attributedText = text + } + + public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { + ASInsetLayoutSpec( + insets: insets, + child: ASStackLayoutSpec( + direction: .vertical, + spacing: 4, + justifyContent: .start, + alignItems: .start, + children: [titleNode, textNode] + ) + ) + } +} From 48b3cc89cf2c0a2ab2e5c3c60feb2f5fae523484 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 13 Oct 2021 23:52:52 +0300 Subject: [PATCH 05/10] issue #553 use decorator for contact key detail screen --- .../ContactDetailViewController.swift | 2 +- .../ContactKeyDetailDecorator.swift | 49 ++++-------- .../ContactKeyDetailViewController.swift | 78 ++++++++----------- .../Resources/en.lproj/Localizable.strings | 11 +++ 4 files changed, 60 insertions(+), 80 deletions(-) diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift index 4eb9dcba9..42616825b 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift @@ -11,7 +11,7 @@ import FlowCryptCommon import FlowCryptUI /** - * View controller which shows details about a contact and the public key recorded for it + * View controller which shows details about a contact and lists public keys recorded for it * - User can be redirected here from settings *ContactsListViewController* by tapping on a particular contact */ final class ContactDetailViewController: TableNodeViewController { diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailDecorator.swift index 0249e4a25..0823b6801 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailDecorator.swift @@ -11,40 +11,25 @@ import Foundation protocol ContactKeyDetailDecoratorType { var title: String { get } - func userNodeInput(with contact: Contact) -> ContactUserCellNode.Input - func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input + func attributedTitle(for contactKeyPart: ContactKeyDetailViewController.Part) -> NSAttributedString } struct ContactKeyDetailDecorator: ContactKeyDetailDecoratorType { - let title = "contact_detail_screen_title".localized - - func userNodeInput(with contact: Contact) -> ContactUserCellNode.Input { - ContactUserCellNode.Input( - user: (contact.name ?? contact.email).attributed(.regular(16)) - ) - } - - func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input { - let df = DateFormatter() - df.dateStyle = .medium - df.timeStyle = .medium - - let fingerpringString = key.fingerprint ?? "-" - - let createdString: String = { - guard let created = key.created else { return "-" } - return df.string(from: created) - }() - - let expiresString: String = { - guard let expires = key.expiresOn else { return "never" } - return df.string(from: expires) - }() - - return ContactKeyCellNode.Input( - fingerprint: fingerpringString.attributed(.regular(13)), - createdAt: createdString.attributed(.regular(14)), - expires: expiresString.attributed(.regular(14)) - ) + let title = "contact_key_detail_screen_title".localized + + func attributedTitle(for contactKeyPart: ContactKeyDetailViewController.Part) -> NSAttributedString { + let title: String + switch contactKeyPart { + case .key: title = "contact_key_pub".localized + case .signature: title = "contact_key_signature".localized + case .created: title = "contact_key_created".localized + case .checked: title = "contact_key_fetched".localized + case .expire: title = "contact_key_expires".localized + case .longids: title = "contact_key_longids".localized + case .fingerprints: title = "contact_key_fingerprints".localized + case .algo: title = "contact_key_algo".localized + } + + return title.attributed(.bold(16)) } } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift index e024d818e..926222930 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift @@ -11,8 +11,8 @@ import FlowCryptCommon import FlowCryptUI /** - * View controller which shows details about a contact and the public key recorded for it - * - User can be redirected here from settings *ContactsListViewController* by tapping on a particular contact + * View controller which shows details about contact's public key + * - User can be redirected here from *ContactDetailViewController* by tapping on a particular key */ final class ContactKeyDetailViewController: TableNodeViewController { typealias ContactKeyDetailAction = (Action) -> Void @@ -21,8 +21,8 @@ final class ContactKeyDetailViewController: TableNodeViewController { case delete(_ key: ContactKey) } - private enum Section: Int, CaseIterable { - case key = 0, signature, checked, expire, longids, fingerprints, created, algo + enum Part: Int, CaseIterable { + case key = 0, signature, created, checked, expire, longids, fingerprints, algo } private let decorator: ContactKeyDetailDecoratorType @@ -87,69 +87,53 @@ extension ContactKeyDetailViewController { } extension ContactKeyDetailViewController: ASTableDelegate, ASTableDataSource { - func numberOfSections(in tableNode: ASTableNode) -> Int { - Section.allCases.count - } - func tableNode(_: ASTableNode, numberOfRowsInSection section: Int) -> Int { - 1 + Part.allCases.count } func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { { [weak self] in - guard let self = self, let section = Section(rawValue: indexPath.section) + guard let self = self, let part = Part(rawValue: indexPath.row) else { return ASCellNode() } - return self.node(for: section, row: indexPath.row) + return self.node(for: part) } } } // MARK: - UI extension ContactKeyDetailViewController { - private func node(for section: Section, row: Int) -> ASCellNode { - let df = DateFormatter() - df.dateStyle = .medium - df.timeStyle = .medium - - switch section { + private func node(for part: Part) -> ASCellNode { + LabelCellNode(title: decorator.attributedTitle(for: part), + text: content(for: part).attributed(.regular(14))) + } + + private func content(for part: Part) -> String { + switch part { case .key: - return LabelCellNode(title: "Key".attributed(.bold(16)), - text: key.key.attributed(.regular(14))) + return key.key case .signature: - return LabelCellNode(title: "Signature".attributed(.bold(16)), - text: "Text".attributed(.regular(14))) + return string(from: key.lastSig) + case .created: + return string(from: key.created) case .checked: - let checkedString: String = { - guard let created = key.lastChecked else { return "-" } - return df.string(from: created) - }() - return LabelCellNode(title: "Last fetched".attributed(.bold(16)), - text: checkedString.attributed(.regular(14))) + return string(from: key.lastChecked) case .expire: - let expireString: String = { - guard let expires = key.expiresOn else { return "-" } - return df.string(from: expires) - }() - return LabelCellNode(title: "Expires".attributed(.bold(16)), - text: expireString.attributed(.regular(14))) + return string(from: key.expiresOn) case .longids: - return LabelCellNode(title: "Longids".attributed(.bold(16)), - text: key.longids.joined(separator: ", ").attributed(.regular(14))) + return key.longids.joined(separator: ", ") case .fingerprints: - return LabelCellNode(title: "Fingerprints".attributed(.bold(16)), - text: key.fingerprints.joined(separator: ", ").attributed(.regular(14))) - case .created: - let createdString: String = { - guard let created = key.created else { return "-" } - return df.string(from: created) - }() - return LabelCellNode(title: "Created".attributed(.bold(16)), - text: createdString.attributed(.regular(14))) + return key.fingerprints.joined(separator: ", ") case .algo: - let algoString = key.algo?.algorithm ?? "-" - return LabelCellNode(title: "Algo".attributed(.bold(16)), - text: algoString.attributed(.regular(14))) + return key.algo?.algorithm ?? "-" } } + private func string(from date: Date?) -> String { + guard let date = date else { return "-" } + + let df = DateFormatter() + df.dateStyle = .medium + df.timeStyle = .medium + return df.string(from: date) + } } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 0d65c17be..f523a3773 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -143,6 +143,17 @@ "contacts_screen_title" = "Contacts"; "contact_detail_screen_title" = "Public Keys"; "contact_detail_copy" = "Public Key copied to clipboard"; +"contact_key_detail_screen_title" = "Public Key"; + +// Contacts keys +"contact_key_pub" = "Key"; +"contact_key_signature" = "Signature"; +"contact_key_fetched" = "Last fetched"; +"contact_key_expires" = "Expires"; +"contact_key_longids" = "Longids"; +"contact_key_fingerprints" = "Fingerprints"; +"contact_key_created" = "Created"; +"contact_key_algo" = "Algo"; // Key Settings "key_settings_title" = "Keys"; From 5be6d448eee8a82750853fc2d20665c7727703b6 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 14 Oct 2021 12:40:55 +0300 Subject: [PATCH 06/10] issue #553 add public keys deletion --- .../ContactDetailViewController.swift | 12 +++--- .../ContactKeyDetailViewController.swift | 2 +- .../Contacts Services/ContactsService.swift | 6 +-- .../LocalContactsProvider.swift | 42 +++++++++---------- .../Contacts Services/Models/Contact.swift | 12 ++++-- 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift index 42616825b..d21ae80cd 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift @@ -26,15 +26,18 @@ final class ContactDetailViewController: TableNodeViewController { } private let decorator: ContactDetailDecoratorType - private let contact: Contact + private let contactsProvider: LocalContactsProviderType + private var contact: Contact private let action: ContactDetailAction? init( decorator: ContactDetailDecoratorType = ContactDetailDecorator(), + contactsProvider: LocalContactsProviderType = LocalContactsProvider(), contact: Contact, action: ContactDetailAction? ) { self.decorator = decorator + self.contactsProvider = contactsProvider self.contact = contact self.action = action super.init(node: TableNode()) @@ -80,15 +83,14 @@ extension ContactDetailViewController { assertionFailure("Can't find index of the contact") return } - indexPathToRemove = IndexPath(row: index, section: 0) + indexPathToRemove = IndexPath(row: index, section: 1) case .right(let indexPath): indexPathToRemove = indexPath keyToRemove = contact.pubKeys[indexPath.row] } - // TODO: Contact provider - // contact.pubKeys.remove(at: indexPathToRemove.row) - + contact.remove(pubKey: keyToRemove) + contactsProvider.remove(pubKey: keyToRemove.key, for: contact.email) node.deleteRows(at: [indexPathToRemove], with: .left) } } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift index 926222930..a97a9985c 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift @@ -58,7 +58,7 @@ final class ContactKeyDetailViewController: TableNodeViewController { with: [ .init(image: UIImage(named: "share"), action: (self, #selector(handleSaveAction))), .init(image: UIImage(named: "copy"), action: (self, #selector(handleCopyAction))), - .init(image: UIImage(named: "trash"), action: (self, #selector(handleRemoveAction))) + .init(image: UIImage(systemName: "trash"), action: (self, #selector(handleRemoveAction))) ] ) } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift index 446ec72ca..837055116 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift @@ -23,7 +23,7 @@ protocol ContactsProviderType { protocol PublicKeyProvider { func retrievePubKeys(for email: String) -> [String] - func remove(pubKey: ContactKey, for email: String) + func remove(pubKey: String, for email: String) } // MARK: - PROVIDER @@ -65,7 +65,7 @@ extension ContactsService: PublicKeyProvider { return publicKeys } - func remove(pubKey: ContactKey, for email: String) { - //let keys = retrievePubKeys(for: email).filter { $0 != pubKey } + func remove(pubKey: String, for email: String) { + localContactsProvider.remove(pubKey: pubKey, for: email) } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift index c99473be2..64ee97718 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift @@ -16,6 +16,7 @@ protocol LocalContactsProviderType: PublicKeyProvider { func save(contact: Contact) func remove(contact: Contact) func getAllContacts() -> [Contact] + func remove(pubKey: String, for email: String) } struct LocalContactsProvider { @@ -33,9 +34,7 @@ struct LocalContactsProvider { extension LocalContactsProvider: LocalContactsProviderType { func updateLastUsedDate(for email: String) { - let contact = localContactsCache.realm - .objects(ContactObject.self) - .first(where: { $0.email == email }) + let contact = find(with: email) try? localContactsCache.realm.write { contact?.lastUsed = Date() @@ -43,10 +42,7 @@ extension LocalContactsProvider: LocalContactsProviderType { } func retrievePubKeys(for email: String) -> [String] { - localContactsCache.encryptedStorage.storage - .objects(ContactObject.self) - .first(where: { $0.email == email })? - .pubKeys + find(with: email)?.pubKeys .map { $0.key } ?? [] } @@ -62,10 +58,8 @@ extension LocalContactsProvider: LocalContactsProviderType { } func searchContact(with email: String) -> Contact? { - localContactsCache.realm - .objects(ContactObject.self) - .first(where: { $0.email == email }) - .map { Contact($0) } + guard let contactObject = find(with: email) else { return nil } + return Contact(contactObject) } func getAllContacts() -> [Contact] { @@ -80,17 +74,21 @@ extension LocalContactsProvider: LocalContactsProviderType { .sorted(by: { $0.email > $1.email }) } - func remove(pubKey: ContactKey, for email: String) { - guard let contact = searchContact(with: email) else { - // TODO: Handle - return - } + func remove(pubKey: String, for email: String) { + find(with: email)? + .pubKeys + .filter { $0.key == pubKey } + .forEach { key in + try? localContactsCache.realm.write { + localContactsCache.realm.delete(key) + } + } + } +} - let updatedContact = Contact(email: contact.email, - name: contact.name, - pubKeys: contact.pubKeys.filter { $0 != pubKey }, - lastUsed: contact.lastUsed) - - save(contact: updatedContact) +extension LocalContactsProvider { + private func find(with email: String) -> ContactObject? { + localContactsCache.realm.object(ofType: ContactObject.self, + forPrimaryKey: email) } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift b/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift index 3238a86e6..55de4c9c7 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift @@ -12,18 +12,18 @@ struct Contact { let email: String /// name if known let name: String? - /// public keys - let pubKeys: [ContactKey] /// last time an email was sent to this contact, update when email is sent let lastUsed: Date? + /// public keys + var pubKeys: [ContactKey] } extension Contact { init(_ contactObject: ContactObject, keyDetails: [KeyDetails] = []) { self.email = contactObject.email self.name = contactObject.name.nilIfEmpty - self.pubKeys = keyDetails.map(ContactKey.init) self.lastUsed = contactObject.lastUsed + self.pubKeys = keyDetails.map(ContactKey.init) } } @@ -36,6 +36,12 @@ extension Contact { } } +extension Contact { + mutating func remove(pubKey: ContactKey) { + pubKeys.removeAll(where: { $0 == pubKey }) + } +} + extension Contact: Equatable { static func == (lhs: Contact, rhs: Contact) -> Bool { lhs.email == rhs.email From 06d25884a792348568b5d3edf58b9f6f1bcda8c3 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 14 Oct 2021 16:22:37 +0300 Subject: [PATCH 07/10] issue #553 refresh contacts list on key delete --- .../ContactsListViewController.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift index 0140bfa1b..2a4dad380 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift @@ -19,6 +19,7 @@ final class ContactsListViewController: TableNodeViewController { private let decorator: ContactsListDecoratorType private let contactsProvider: LocalContactsProviderType private var contacts: [Contact] = [] + private var selectedIndexPath: IndexPath? init( decorator: ContactsListDecoratorType = ContactsListDecorator(), @@ -39,6 +40,11 @@ final class ContactsListViewController: TableNodeViewController { setupUI() fetchContacts() } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + reloadContacts() + } } extension ContactsListViewController { @@ -48,6 +54,13 @@ extension ContactsListViewController { title = decorator.title } + private func reloadContacts() { + guard let indexPath = selectedIndexPath else { return } + fetchContacts() + node.reloadRows(at: [indexPath], with: .automatic) + selectedIndexPath = nil + } + private func fetchContacts() { contacts = contactsProvider.getAllContacts() } @@ -92,7 +105,7 @@ extension ContactsListViewController { } self?.delete(with: .left(contact)) } - + selectedIndexPath = indexPath navigationController?.pushViewController(contactDetailViewController, animated: true) } From e7bf2e883cd6c1daebe246b85e89a07f5986d1ec Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 14 Oct 2021 16:33:37 +0300 Subject: [PATCH 08/10] issue #553 update tests for multiple contact pub keys --- .../Services/ComposeMessageServiceTests.swift | 14 +++++++------- FlowCryptAppTests/Mocks/ContactsServiceMock.swift | 8 +++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift index 77c492dd3..20e24bb30 100644 --- a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift +++ b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift @@ -192,8 +192,8 @@ class ComposeMessageServiceTests: XCTestCase { } recipients.forEach { recipient in - contactsService.retrievePubKeyResult = { _ in - nil + contactsService.retrievePubKeysResult = { _ in + [] } } @@ -220,11 +220,11 @@ class ComposeMessageServiceTests: XCTestCase { let recWithoutPubKey = recipients[0].email recipients.forEach { _ in - contactsService.retrievePubKeyResult = { recipient in + contactsService.retrievePubKeysResult = { recipient in if recipient == recWithoutPubKey { - return nil + return [] } - return "recipient pub key" + return ["recipient pub key"] } } @@ -250,8 +250,8 @@ class ComposeMessageServiceTests: XCTestCase { } recipients.enumerated().forEach { (element, index) in - contactsService.retrievePubKeyResult = { recipient in - "pubKey" + contactsService.retrievePubKeysResult = { recipient in + ["pubKey"] } } diff --git a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift index 07b9a1fb0..eb961e8a7 100644 --- a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift +++ b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift @@ -11,13 +11,15 @@ import Promises @testable import FlowCrypt class ContactsServiceMock: ContactsServiceType { - var retrievePubKeyResult: ((String) -> (String?))! - func retrievePubKey(for email: String) -> String? { - retrievePubKeyResult(email) + var retrievePubKeysResult: ((String) -> ([String]))! + func retrievePubKeys(for email: String) -> [String] { + retrievePubKeysResult(email) } var searchContactResult: Result! func searchContact(with email: String) -> Promise { Promise.resolveAfter(with: searchContactResult) } + + func remove(pubKey: String, for email: String) {} } From 7b15d7a1dd693f4aa0afdc15bff8adf596bcb9b1 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Fri, 15 Oct 2021 15:58:12 +0300 Subject: [PATCH 09/10] issue #553 rename Contact to RecipientWithPubKeys --- FlowCrypt.xcodeproj/project.pbxproj | 43 +++++------------ .../xcshareddata/swiftpm/Package.resolved | 9 ---- .../ContactDetailDecorator.swift | 8 ++-- .../ContactDetailViewController.swift | 34 +++++++------- .../ContactKeyDetailViewController.swift | 30 ++++++------ .../Contacts List/ContactsListDecorator.swift | 16 +++---- .../ContactsListViewController.swift | 26 +++++----- .../ComposeMessageService.swift | 6 +-- .../Contacts Services/ContactsService.swift | 18 +++---- .../LocalContactsProvider.swift | 47 +++++++++---------- .../Models/{ContactKey.swift => PubKey.swift} | 16 +++---- ...ntact.swift => RecipientWithPubKeys.swift} | 28 +++++------ .../Functionality/WKDURLs/PubLookup.swift | 20 +++----- .../Realm Models/ContactKeyObject.swift | 10 ++-- .../Models/Realm Models/ContactObject.swift | 24 +++++----- 15 files changed, 153 insertions(+), 182 deletions(-) rename FlowCrypt/Functionality/Services/Contacts Services/Models/{ContactKey.swift => PubKey.swift} (81%) rename FlowCrypt/Functionality/Services/Contacts Services/Models/{Contact.swift => RecipientWithPubKeys.swift} (50%) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index e30500c7f..cfbccdc01 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -52,7 +52,6 @@ 32DCAF95A6A329C3136B1C8E /* Imap+msg.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA55C094E9745AA1FD210 /* Imap+msg.swift */; }; 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA9701B2D5052225A0414 /* SignInViewController.swift */; }; 50531BE42629B9A80039BAE9 /* AttachmentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50531BE32629B9A80039BAE9 /* AttachmentNode.swift */; }; - 510A260126FDFEBE00163271 /* MailCore2 in Frameworks */ = {isa = PBXBuildFile; productRef = 510A260026FDFEBE00163271 /* MailCore2 */; }; 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */ = {isa = PBXBuildFile; productRef = 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */; }; 5133B6702716320F00C95463 /* ContactKeyDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */; }; 5133B6722716321F00C95463 /* ContactKeyDetailDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B6712716321F00C95463 /* ContactKeyDetailDecorator.swift */; }; @@ -61,7 +60,7 @@ 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; 51B4AE4F27137CB70001F33B /* Promises in Frameworks */ = {isa = PBXBuildFile; productRef = 51B4AE4E27137CB70001F33B /* Promises */; }; 51B4AE51271444580001F33B /* ContactKeyObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE50271444580001F33B /* ContactKeyObject.swift */; }; - 51B4AE5327144E590001F33B /* ContactKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE5227144E590001F33B /* ContactKey.swift */; }; + 51B4AE5327144E590001F33B /* PubKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE5227144E590001F33B /* PubKey.swift */; }; 51DE2FEE2714DA0400916222 /* ContactKeyCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DE2FED2714DA0400916222 /* ContactKeyCellNode.swift */; }; 51E13F12270F92BA00F287CA /* IDZSwiftCommonCrypto in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */; }; 51E13F15270F92F200F287CA /* BigInt in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F14270F92F200F287CA /* BigInt */; }; @@ -288,7 +287,7 @@ D274724424FD1932006BA6EF /* FolderObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274724324FD1932006BA6EF /* FolderObject.swift */; }; D27B911924EFE79F002DF0A1 /* LocalContactsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27B911824EFE79F002DF0A1 /* LocalContactsProvider.swift */; }; D27B911D24EFE806002DF0A1 /* ContactObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27B911C24EFE806002DF0A1 /* ContactObject.swift */; }; - D27B911F24EFE828002DF0A1 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27B911E24EFE828002DF0A1 /* Contact.swift */; }; + D27B911F24EFE828002DF0A1 /* RecipientWithPubKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27B911E24EFE828002DF0A1 /* RecipientWithPubKeys.swift */; }; D28325DD24895F5700D311BD /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 949ED9412303E3B400530579 /* Colors.xcassets */; }; D28655912423B4580066F52E /* HeaderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF3639235A0B3B00614596 /* HeaderNode.swift */; }; D28655932423B4EE0066F52E /* MyMenuViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28655922423B4EE0066F52E /* MyMenuViewDecorator.swift */; }; @@ -475,7 +474,7 @@ 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrvKeyInfoTests.swift; sourceTree = ""; }; 51775C38270C7D2400D7C944 /* StorageMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageMethod.swift; sourceTree = ""; }; 51B4AE50271444580001F33B /* ContactKeyObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyObject.swift; sourceTree = ""; }; - 51B4AE5227144E590001F33B /* ContactKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKey.swift; sourceTree = ""; }; + 51B4AE5227144E590001F33B /* PubKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubKey.swift; sourceTree = ""; }; 51DE2FED2714DA0400916222 /* ContactKeyCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyCellNode.swift; sourceTree = ""; }; 51E1673C270DAFF900D27C52 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; 55652F68438D6EDFE71EA13C /* Pods-FlowCryptUIApplication.enterprise.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUIApplication.enterprise.xcconfig"; path = "Target Support Files/Pods-FlowCryptUIApplication/Pods-FlowCryptUIApplication.enterprise.xcconfig"; sourceTree = ""; }; @@ -714,7 +713,7 @@ D274724324FD1932006BA6EF /* FolderObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderObject.swift; sourceTree = ""; }; D27B911824EFE79F002DF0A1 /* LocalContactsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalContactsProvider.swift; sourceTree = ""; }; D27B911C24EFE806002DF0A1 /* ContactObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactObject.swift; sourceTree = ""; }; - D27B911E24EFE828002DF0A1 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; + D27B911E24EFE828002DF0A1 /* RecipientWithPubKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientWithPubKeys.swift; sourceTree = ""; }; D28655922423B4EE0066F52E /* MyMenuViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyMenuViewDecorator.swift; sourceTree = ""; }; D28655942423BFF60066F52E /* SideMenuOptionalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuOptionalView.swift; sourceTree = ""; }; D2891AC124C59EFA008918E3 /* KeyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyService.swift; sourceTree = ""; }; @@ -783,7 +782,6 @@ D2CDC3DD24052D50002B045F /* FlowCryptUI.framework in Frameworks */, 51E13F15270F92F200F287CA /* BigInt in Frameworks */, 51E16746270F301F00D27C52 /* GoogleAPIClientForREST_Gmail in Frameworks */, - 510A260126FDFEBE00163271 /* MailCore2 in Frameworks */, 51E1675D270F36A400D27C52 /* Realm in Frameworks */, 51E13F12270F92BA00F287CA /* IDZSwiftCommonCrypto in Frameworks */, 51E13F18270F934C00F287CA /* MBProgressHUD in Frameworks */, @@ -1805,8 +1803,8 @@ D27B912024EFE842002DF0A1 /* Models */ = { isa = PBXGroup; children = ( - D27B911E24EFE828002DF0A1 /* Contact.swift */, - 51B4AE5227144E590001F33B /* ContactKey.swift */, + D27B911E24EFE828002DF0A1 /* RecipientWithPubKeys.swift */, + 51B4AE5227144E590001F33B /* PubKey.swift */, ); path = Models; sourceTree = ""; @@ -2081,7 +2079,6 @@ ); name = FlowCrypt; packageProductDependencies = ( - 510A260026FDFEBE00163271 /* MailCore2 */, 51E16745270F301F00D27C52 /* GoogleAPIClientForREST_Gmail */, 51E16748270F303100D27C52 /* GoogleSignIn */, 51E1675C270F36A400D27C52 /* Realm */, @@ -2198,7 +2195,6 @@ ); mainGroup = C132B9A71EC2DBD800763715; packageReferences = ( - 510A25FF26FDFEBE00163271 /* XCRemoteSwiftPackageReference "mailcore2" */, 51E16744270F301F00D27C52 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */, 51E16747270F303100D27C52 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, 51E1675B270F36A400D27C52 /* XCRemoteSwiftPackageReference "realm-cocoa" */, @@ -2496,7 +2492,7 @@ 9FB22CE425715D3E0026EE64 /* GmailServiceErrorHandler.swift in Sources */, 9F4163E6266520B600106194 /* CommonNodesInputs.swift in Sources */, 04B472951ECE29F600B8266F /* MyMenuViewController.swift in Sources */, - 51B4AE5327144E590001F33B /* ContactKey.swift in Sources */, + 51B4AE5327144E590001F33B /* PubKey.swift in Sources */, 21623D1826FA860700A11B9A /* PhotosManager.swift in Sources */, C132B9B41EC2DBD800763715 /* AppDelegate.swift in Sources */, 21489B7C267CBA0E00BDE4AC /* ClientConfiguration.swift in Sources */, @@ -2583,7 +2579,7 @@ 9F3EF33123B1785600FA0CEF /* MsgListViewConroller.swift in Sources */, 9F31ABA0232C071700CF87EA /* GlobalRouter.swift in Sources */, 9F5C2A92257E94DF00DE9B4B /* Imap+MessageOperations.swift in Sources */, - D27B911F24EFE828002DF0A1 /* Contact.swift in Sources */, + D27B911F24EFE828002DF0A1 /* RecipientWithPubKeys.swift in Sources */, 32DCA1414EEA727B86C337D5 /* Core.swift in Sources */, 9FB22CF725715DC50026EE64 /* KeyServiceErrorHandler.swift in Sources */, 9F0C3C1A231819C500299985 /* MessageKindProviderType.swift in Sources */, @@ -3107,7 +3103,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = W57XRJ27NX; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", @@ -3194,7 +3190,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = W57XRJ27NX; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FlowCryptUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -3402,7 +3398,7 @@ ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -3496,7 +3492,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = W57XRJ27NX; ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", @@ -3725,7 +3721,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FlowCryptCommon/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3975,14 +3971,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 510A25FF26FDFEBE00163271 /* XCRemoteSwiftPackageReference "mailcore2" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/MailCore/mailcore2"; - requirement = { - branch = master; - kind = branch; - }; - }; 51B4AE4D27137CB70001F33B /* XCRemoteSwiftPackageReference "promises" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/google/promises"; @@ -4042,11 +4030,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 510A260026FDFEBE00163271 /* MailCore2 */ = { - isa = XCSwiftPackageProductDependency; - package = 510A25FF26FDFEBE00163271 /* XCRemoteSwiftPackageReference "mailcore2" */; - productName = MailCore2; - }; 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */ = { isa = XCSwiftPackageProductDependency; package = 51E16744270F301F00D27C52 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */; diff --git a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved index 44d4ba74b..d9e61d900 100644 --- a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -64,15 +64,6 @@ "version": "0.13.1" } }, - { - "package": "MailCore2", - "repositoryURL": "https://github.com/MailCore/mailcore2", - "state": { - "branch": "master", - "revision": "1434761709b4d9dd5acc5b7185a3b32aa4845ba8", - "version": null - } - }, { "package": "MBProgressHUD", "repositoryURL": "https://github.com/jdg/MBProgressHUD", diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift index cb0a02ce3..37e351bad 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailDecorator.swift @@ -11,20 +11,20 @@ import Foundation protocol ContactDetailDecoratorType { var title: String { get } - func userNodeInput(with contact: Contact) -> ContactUserCellNode.Input - func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input + func userNodeInput(with contact: RecipientWithPubKeys) -> ContactUserCellNode.Input + func keyNodeInput(with key: PubKey) -> ContactKeyCellNode.Input } struct ContactDetailDecorator: ContactDetailDecoratorType { let title = "contact_detail_screen_title".localized - func userNodeInput(with contact: Contact) -> ContactUserCellNode.Input { + func userNodeInput(with contact: RecipientWithPubKeys) -> ContactUserCellNode.Input { ContactUserCellNode.Input( user: (contact.name ?? contact.email).attributed(.regular(16)) ) } - func keyNodeInput(with key: ContactKey) -> ContactKeyCellNode.Input { + func keyNodeInput(with key: PubKey) -> ContactKeyCellNode.Input { let df = DateFormatter() df.dateStyle = .medium df.timeStyle = .medium diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift index d21ae80cd..b57de248e 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactDetailViewController.swift @@ -18,7 +18,7 @@ final class ContactDetailViewController: TableNodeViewController { typealias ContactDetailAction = (Action) -> Void enum Action { - case delete(_ contact: Contact) + case delete(_ recipient: RecipientWithPubKeys) } private enum Section: Int, CaseIterable { @@ -27,18 +27,18 @@ final class ContactDetailViewController: TableNodeViewController { private let decorator: ContactDetailDecoratorType private let contactsProvider: LocalContactsProviderType - private var contact: Contact + private var recipient: RecipientWithPubKeys private let action: ContactDetailAction? init( decorator: ContactDetailDecoratorType = ContactDetailDecorator(), contactsProvider: LocalContactsProviderType = LocalContactsProvider(), - contact: Contact, + recipient: RecipientWithPubKeys, action: ContactDetailAction? ) { self.decorator = decorator self.contactsProvider = contactsProvider - self.contact = contact + self.recipient = recipient self.action = action super.init(node: TableNode()) } @@ -69,28 +69,30 @@ extension ContactDetailViewController { @objc private final func handleRemoveAction() { navigationController?.popViewController(animated: true) { [weak self] in guard let self = self else { return } - self.action?(.delete(self.contact)) + self.action?(.delete(self.recipient)) } } - private func delete(with context: Either) { - let keyToRemove: ContactKey + private func delete(with context: Either) { + let keyToRemove: PubKey let indexPathToRemove: IndexPath switch context { case .left(let key): keyToRemove = key - guard let index = contact.pubKeys.firstIndex(where: { $0 == key }) else { + guard let index = recipient.pubKeys.firstIndex(where: { $0 == key }) else { assertionFailure("Can't find index of the contact") return } indexPathToRemove = IndexPath(row: index, section: 1) case .right(let indexPath): indexPathToRemove = indexPath - keyToRemove = contact.pubKeys[indexPath.row] + keyToRemove = recipient.pubKeys[indexPath.row] } - contact.remove(pubKey: keyToRemove) - contactsProvider.remove(pubKey: keyToRemove.key, for: contact.email) + recipient.remove(pubKey: keyToRemove) + if let fingerprint = keyToRemove.fingerprint, fingerprint.isNotEmpty { + contactsProvider.removePubKey(with: fingerprint, for: recipient.email) + } node.deleteRows(at: [indexPathToRemove], with: .left) } } @@ -105,7 +107,7 @@ extension ContactDetailViewController: ASTableDelegate, ASTableDataSource { switch section { case .header: return 1 - case .keys: return contact.pubKeys.count + case .keys: return recipient.pubKeys.count } } @@ -124,8 +126,8 @@ extension ContactDetailViewController: ASTableDelegate, ASTableDataSource { case .header: return case .keys: - let key = contact.pubKeys[indexPath.row] - let contactKeyDetailViewController = ContactKeyDetailViewController(key: key) { [weak self] action in + let pubKey = recipient.pubKeys[indexPath.row] + let contactKeyDetailViewController = ContactKeyDetailViewController(pubKey: pubKey) { [weak self] action in guard case let .delete(key) = action else { assertionFailure("Action is not implemented") return @@ -143,10 +145,10 @@ extension ContactDetailViewController { private func node(for section: Section, row: Int) -> ASCellNode { switch section { case .header: - return ContactUserCellNode(input: self.decorator.userNodeInput(with: self.contact)) + return ContactUserCellNode(input: self.decorator.userNodeInput(with: self.recipient)) case .keys: return ContactKeyCellNode( - input: self.decorator.keyNodeInput(with: self.contact.pubKeys[row]) + input: self.decorator.keyNodeInput(with: self.recipient.pubKeys[row]) ) } } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift index a97a9985c..38af05f98 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactKeyDetailViewController.swift @@ -18,7 +18,7 @@ final class ContactKeyDetailViewController: TableNodeViewController { typealias ContactKeyDetailAction = (Action) -> Void enum Action { - case delete(_ key: ContactKey) + case delete(_ key: PubKey) } enum Part: Int, CaseIterable { @@ -26,16 +26,16 @@ final class ContactKeyDetailViewController: TableNodeViewController { } private let decorator: ContactKeyDetailDecoratorType - private let key: ContactKey + private let pubKey: PubKey private let action: ContactKeyDetailAction? init( decorator: ContactKeyDetailDecoratorType = ContactKeyDetailDecorator(), - key: ContactKey, + pubKey: PubKey, action: ContactKeyDetailAction? ) { self.decorator = decorator - self.key = key + self.pubKey = pubKey self.action = action super.init(node: TableNode()) } @@ -67,21 +67,21 @@ final class ContactKeyDetailViewController: TableNodeViewController { extension ContactKeyDetailViewController { @objc private final func handleSaveAction() { let vc = UIActivityViewController( - activityItems: [key.key], + activityItems: [pubKey.key], applicationActivities: nil ) present(vc, animated: true, completion: nil) } @objc private final func handleCopyAction() { - UIPasteboard.general.string = key.key + UIPasteboard.general.string = pubKey.key showToast("contact_detail_copy".localized) } @objc private final func handleRemoveAction() { navigationController?.popViewController(animated: true) { [weak self] in guard let self = self else { return } - self.action?(.delete(self.key)) + self.action?(.delete(self.pubKey)) } } } @@ -110,21 +110,21 @@ extension ContactKeyDetailViewController { private func content(for part: Part) -> String { switch part { case .key: - return key.key + return pubKey.key case .signature: - return string(from: key.lastSig) + return string(from: pubKey.lastSig) case .created: - return string(from: key.created) + return string(from: pubKey.created) case .checked: - return string(from: key.lastChecked) + return string(from: pubKey.lastChecked) case .expire: - return string(from: key.expiresOn) + return string(from: pubKey.expiresOn) case .longids: - return key.longids.joined(separator: ", ") + return pubKey.longids.joined(separator: ", ") case .fingerprints: - return key.fingerprints.joined(separator: ", ") + return pubKey.fingerprints.joined(separator: ", ") case .algo: - return key.algo?.algorithm ?? "-" + return pubKey.algo?.algorithm ?? "-" } } diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift index 0dd0a8270..8e100bb87 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListDecorator.swift @@ -11,27 +11,27 @@ import UIKit protocol ContactsListDecoratorType { var title: String { get } - func contactNodeInput(with contact: Contact) -> ContactCellNode.Input + func contactNodeInput(with recipient: RecipientWithPubKeys) -> ContactCellNode.Input } struct ContactsListDecorator: ContactsListDecoratorType { let title = "contacts_screen_title".localized - func contactNodeInput(with contact: Contact) -> ContactCellNode.Input { + func contactNodeInput(with recipient: RecipientWithPubKeys) -> ContactCellNode.Input { let name: String - if let contactName = contact.name, contactName.isNotEmpty { - let components = contactName + if let recipientName = recipient.name, recipientName.isNotEmpty { + let components = recipientName .split(separator: " ") .filter { !$0.contains("@") } if components.isEmpty { - name = nameFrom(email: contact.email) + name = nameFrom(email: recipient.email) } else { name = components.joined(separator: " ") } } else { - name = nameFrom(email: contact.email) + name = nameFrom(email: recipient.email) } let buttonColor = UIColor.colorFor( @@ -39,11 +39,11 @@ struct ContactsListDecorator: ContactsListDecoratorType { lightStyle: .darkGray ) - let keysCount = "%@ public key(s)".localizeWithArguments(contact.pubKeys.count) + let keysCount = "%@ public key(s)".localizeWithArguments(recipient.pubKeys.count) return ContactCellNode.Input( name: name.attributed(.medium(16)), - email: contact.email.attributed(.medium(14)), + email: recipient.email.attributed(.medium(14)), keys: "(\(keysCount))".attributed(.medium(14), color: .mainTextColor.withAlphaComponent(0.5)), insets: UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16), buttonImage: UIImage(systemName: "trash")?.tinted(buttonColor) diff --git a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift index 2a4dad380..33a73b2b1 100644 --- a/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift +++ b/FlowCrypt/Controllers/Settings/Contacts/Contacts List/ContactsListViewController.swift @@ -18,7 +18,7 @@ import FlowCryptUI final class ContactsListViewController: TableNodeViewController { private let decorator: ContactsListDecoratorType private let contactsProvider: LocalContactsProviderType - private var contacts: [Contact] = [] + private var recipients: [RecipientWithPubKeys] = [] private var selectedIndexPath: IndexPath? init( @@ -62,20 +62,20 @@ extension ContactsListViewController { } private func fetchContacts() { - contacts = contactsProvider.getAllContacts() + recipients = contactsProvider.getAllRecipients() } } extension ContactsListViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { - contacts.count + recipients.count } func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { return { [weak self] in guard let self = self else { return ASCellNode() } return ContactCellNode( - input: self.decorator.contactNodeInput(with: self.contacts[indexPath.row]), + input: self.decorator.contactNodeInput(with: self.recipients[indexPath.row]), action: { [weak self] in self?.handleDeleteButtonTap(with: indexPath) } @@ -97,7 +97,7 @@ extension ContactsListViewController { private func proceedToContactDetail(with indexPath: IndexPath) { let contactDetailViewController = ContactDetailViewController( - contact: contacts[indexPath.row] + recipient: recipients[indexPath.row] ) { [weak self] action in guard case let .delete(contact) = action else { assertionFailure("Action is not implemented") @@ -109,24 +109,24 @@ extension ContactsListViewController { navigationController?.pushViewController(contactDetailViewController, animated: true) } - private func delete(with context: Either) { - let contactToRemove: Contact + private func delete(with context: Either) { + let recipientToRemove: RecipientWithPubKeys let indexPathToRemove: IndexPath switch context { - case .left(let contact): - contactToRemove = contact - guard let index = contacts.firstIndex(where: { $0 == contact }) else { + case .left(let recipient): + recipientToRemove = recipient + guard let index = recipients.firstIndex(where: { $0 == recipient }) else { assertionFailure("Can't find index of the contact") return } indexPathToRemove = IndexPath(row: index, section: 0) case .right(let indexPath): indexPathToRemove = indexPath - contactToRemove = contacts[indexPath.row] + recipientToRemove = recipients[indexPath.row] } - contactsProvider.remove(contact: contactToRemove) - contacts.remove(at: indexPathToRemove.row) + contactsProvider.remove(recipient: recipientToRemove) + recipients.remove(at: indexPathToRemove.row) node.deleteRows(at: [indexPathToRemove], with: .left) } } diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index b5b6ee8e1..ef58b8b2f 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -113,16 +113,16 @@ final class ComposeMessageService { private func getPubKeys(for recipients: [ComposeMessageRecipient]) -> Result<[String], MessageValidationError> { let pubKeys = recipients.map { - ($0.email, contactsService.retrievePubKeys(for: $0.email)) + (email: $0.email, keys: contactsService.retrievePubKeys(for: $0.email)) } - let emailsWithoutPubKeys = pubKeys.filter { $0.1.isEmpty }.map(\.0) + let emailsWithoutPubKeys = pubKeys.filter { $0.keys.isEmpty }.map(\.email) guard emailsWithoutPubKeys.isEmpty else { return .failure(.noPubRecipients(emailsWithoutPubKeys)) } - return .success(pubKeys.filter({ $0.1.isNotEmpty }).flatMap(\.1)) + return .success(pubKeys.filter({ $0.keys.isNotEmpty }).flatMap(\.keys)) } // MARK: - Encrypt and Send diff --git a/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift b/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift index 837055116..0b1495552 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/ContactsService.swift @@ -18,12 +18,12 @@ protocol ContactsServiceType: PublicKeyProvider, ContactsProviderType { } protocol ContactsProviderType { - func searchContact(with email: String) -> Promise + func searchContact(with email: String) -> Promise } protocol PublicKeyProvider { func retrievePubKeys(for email: String) -> [String] - func remove(pubKey: String, for email: String) + func removePubKey(with fingerprint: String, for email: String) } // MARK: - PROVIDER @@ -42,18 +42,18 @@ struct ContactsService: ContactsServiceType { } extension ContactsService: ContactsProviderType { - func searchContact(with email: String) -> Promise { - guard let contact = localContactsProvider.searchContact(with: email) else { + func searchContact(with email: String) -> Promise { + guard let contact = localContactsProvider.searchRecipient(with: email) else { return searchRemote(for: email) } return Promise(contact) } - private func searchRemote(for email: String) -> Promise { + private func searchRemote(for email: String) -> Promise { pubLookup .lookup(with: email) - .then { contact in - self.localContactsProvider.save(contact: contact) + .then { recipient in + self.localContactsProvider.save(recipient: recipient) } } } @@ -65,7 +65,7 @@ extension ContactsService: PublicKeyProvider { return publicKeys } - func remove(pubKey: String, for email: String) { - localContactsProvider.remove(pubKey: pubKey, for: email) + func removePubKey(with fingerprint: String, for email: String) { + localContactsProvider.removePubKey(with: fingerprint, for: email) } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift index 64ee97718..ceb68cf9c 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift @@ -12,32 +12,31 @@ import RealmSwift protocol LocalContactsProviderType: PublicKeyProvider { func updateLastUsedDate(for email: String) - func searchContact(with email: String) -> Contact? - func save(contact: Contact) - func remove(contact: Contact) - func getAllContacts() -> [Contact] - func remove(pubKey: String, for email: String) + func searchRecipient(with email: String) -> RecipientWithPubKeys? + func save(recipient: RecipientWithPubKeys) + func remove(recipient: RecipientWithPubKeys) + func getAllRecipients() -> [RecipientWithPubKeys] } struct LocalContactsProvider { - private let localContactsCache: CacheService + private let localContactsCache: CacheService let core: Core init( encryptedStorage: EncryptedStorageType = EncryptedStorage(), core: Core = .shared ) { - self.localContactsCache = CacheService(encryptedStorage: encryptedStorage) + self.localContactsCache = CacheService(encryptedStorage: encryptedStorage) self.core = core } } extension LocalContactsProvider: LocalContactsProviderType { func updateLastUsedDate(for email: String) { - let contact = find(with: email) + let recipient = find(with: email) try? localContactsCache.realm.write { - contact?.lastUsed = Date() + recipient?.lastUsed = Date() } } @@ -46,38 +45,38 @@ extension LocalContactsProvider: LocalContactsProviderType { .map { $0.key } ?? [] } - func save(contact: Contact) { - localContactsCache.save(ContactObject(contact)) + func save(recipient: RecipientWithPubKeys) { + localContactsCache.save(RecipientObject(recipient)) } - func remove(contact: Contact) { + func remove(recipient: RecipientWithPubKeys) { localContactsCache.remove( - object: ContactObject(contact), - with: contact.email + object: RecipientObject(recipient), + with: recipient.email ) } - func searchContact(with email: String) -> Contact? { - guard let contactObject = find(with: email) else { return nil } - return Contact(contactObject) + func searchRecipient(with email: String) -> RecipientWithPubKeys? { + guard let recipientObject = find(with: email) else { return nil } + return RecipientWithPubKeys(recipientObject) } - func getAllContacts() -> [Contact] { + func getAllRecipients() -> [RecipientWithPubKeys] { localContactsCache.realm - .objects(ContactObject.self) + .objects(RecipientObject.self) .map { object in let keyDetails = object.pubKeys .compactMap { try? core.parseKeys(armoredOrBinary: $0.key.data()).keyDetails } .flatMap { $0 } - return Contact(object, keyDetails: Array(keyDetails)) + return RecipientWithPubKeys(object, keyDetails: Array(keyDetails)) } .sorted(by: { $0.email > $1.email }) } - func remove(pubKey: String, for email: String) { + func removePubKey(with fingerprint: String, for email: String) { find(with: email)? .pubKeys - .filter { $0.key == pubKey } + .filter { $0.key == fingerprint } .forEach { key in try? localContactsCache.realm.write { localContactsCache.realm.delete(key) @@ -87,8 +86,8 @@ extension LocalContactsProvider: LocalContactsProviderType { } extension LocalContactsProvider { - private func find(with email: String) -> ContactObject? { - localContactsCache.realm.object(ofType: ContactObject.self, + private func find(with email: String) -> RecipientObject? { + localContactsCache.realm.object(ofType: RecipientObject.self, forPrimaryKey: email) } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift b/FlowCrypt/Functionality/Services/Contacts Services/Models/PubKey.swift similarity index 81% rename from FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift rename to FlowCrypt/Functionality/Services/Contacts Services/Models/PubKey.swift index 79ea3d8ef..7c72e10f3 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/Models/ContactKey.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/Models/PubKey.swift @@ -8,17 +8,17 @@ import Foundation -struct ContactKey { +struct PubKey { let key: String /// will be provided later let lastSig: Date? - /// the date when key was retrieved from Attester, or nil + /// the date when key was retrieved from a public key server, or nil let lastChecked: Date? /// expiration date let expiresOn: Date? - /// all key longids, comma-separated + /// all key longids let longids: [String] - /// all key fingerprints, comma-separated + /// all key fingerprints let fingerprints: [String] /// key created date let created: Date? @@ -26,14 +26,14 @@ struct ContactKey { let algo: KeyAlgo? } -extension ContactKey { +extension PubKey { /// first key longid var longid: String? { longids.first } /// first key fingerprint var fingerprint: String? { fingerprints.first } } -extension ContactKey { +extension PubKey { init(keyDetails: KeyDetails) { let keyIds = keyDetails.ids let longids = keyIds.map(\.longid) @@ -50,8 +50,8 @@ extension ContactKey { } } -extension ContactKey: Equatable { - static func == (lhs: ContactKey, rhs: ContactKey) -> Bool { +extension PubKey: Equatable { + static func == (lhs: PubKey, rhs: PubKey) -> Bool { lhs.key == rhs.key } } diff --git a/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift b/FlowCrypt/Functionality/Services/Contacts Services/Models/RecipientWithPubKeys.swift similarity index 50% rename from FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift rename to FlowCrypt/Functionality/Services/Contacts Services/Models/RecipientWithPubKeys.swift index 55de4c9c7..f6dd56e5f 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/Models/Contact.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/Models/RecipientWithPubKeys.swift @@ -8,42 +8,42 @@ import Foundation -struct Contact { +struct RecipientWithPubKeys { let email: String /// name if known let name: String? /// last time an email was sent to this contact, update when email is sent let lastUsed: Date? /// public keys - var pubKeys: [ContactKey] + var pubKeys: [PubKey] } -extension Contact { - init(_ contactObject: ContactObject, keyDetails: [KeyDetails] = []) { - self.email = contactObject.email - self.name = contactObject.name.nilIfEmpty - self.lastUsed = contactObject.lastUsed - self.pubKeys = keyDetails.map(ContactKey.init) +extension RecipientWithPubKeys { + init(_ recipientObject: RecipientObject, keyDetails: [KeyDetails] = []) { + self.email = recipientObject.email + self.name = recipientObject.name.nilIfEmpty + self.lastUsed = recipientObject.lastUsed + self.pubKeys = keyDetails.map(PubKey.init) } } -extension Contact { +extension RecipientWithPubKeys { init(email: String, keyDetails: [KeyDetails]) { self.email = email self.name = keyDetails.first?.users.first ?? email self.lastUsed = nil - self.pubKeys = keyDetails.map(ContactKey.init) + self.pubKeys = keyDetails.map(PubKey.init) } } -extension Contact { - mutating func remove(pubKey: ContactKey) { +extension RecipientWithPubKeys { + mutating func remove(pubKey: PubKey) { pubKeys.removeAll(where: { $0 == pubKey }) } } -extension Contact: Equatable { - static func == (lhs: Contact, rhs: Contact) -> Bool { +extension RecipientWithPubKeys: Equatable { + static func == (lhs: RecipientWithPubKeys, rhs: RecipientWithPubKeys) -> Bool { lhs.email == rhs.email } } diff --git a/FlowCrypt/Functionality/WKDURLs/PubLookup.swift b/FlowCrypt/Functionality/WKDURLs/PubLookup.swift index 8d5d83bb1..1d18560ca 100644 --- a/FlowCrypt/Functionality/WKDURLs/PubLookup.swift +++ b/FlowCrypt/Functionality/WKDURLs/PubLookup.swift @@ -9,7 +9,7 @@ import Promises protocol PubLookupType { - func lookup(with email: String) -> Promise + func lookup(with email: String) -> Promise } class PubLookup: PubLookupType { @@ -24,28 +24,20 @@ class PubLookup: PubLookupType { self.attesterApi = attesterApi } - func lookup(with email: String) -> Promise { - Promise { resolve, _ in - let keyDetails = try awaitPromise(self.getKeyDetails(email)) - resolve(Contact(email: email, keyDetails: keyDetails)) - } - } - - private func getKeyDetails(_ email: String) -> Promise<[KeyDetails]> { - - Promise<[KeyDetails]> { [weak self] resolve, _ in + func lookup(with email: String) -> Promise { + Promise { [weak self] resolve, _ in guard let self = self else { - resolve([]) + resolve(RecipientWithPubKeys(email: email, keyDetails: [])) return } let wkdResult = try awaitPromise(self.wkd.lookupEmail(email)) if !wkdResult.isEmpty { - resolve(wkdResult) + resolve(RecipientWithPubKeys(email: email, keyDetails: wkdResult)) } let attesterResult = try awaitPromise(self.attesterApi.lookupEmail(email: email)) - resolve(attesterResult) + resolve(RecipientWithPubKeys(email: email, keyDetails: attesterResult)) } } } diff --git a/FlowCrypt/Models/Realm Models/ContactKeyObject.swift b/FlowCrypt/Models/Realm Models/ContactKeyObject.swift index 39a93ecb7..3d2e84691 100644 --- a/FlowCrypt/Models/Realm Models/ContactKeyObject.swift +++ b/FlowCrypt/Models/Realm Models/ContactKeyObject.swift @@ -9,7 +9,7 @@ import Foundation import RealmSwift -final class ContactKeyObject: Object { +final class PubKeyObject: Object { @Persisted(primaryKey: true) var key: String = "" @Persisted var lastSig: Date? @@ -39,8 +39,8 @@ final class ContactKeyObject: Object { } } -extension ContactKeyObject { - convenience init(_ key: ContactKey) { +extension PubKeyObject { + convenience init(_ key: PubKey) { self.init( key: key.key, lastSig: key.lastSig, @@ -52,3 +52,7 @@ extension ContactKeyObject { ) } } + +extension PubKeyObject { + var fingerprint: String? { fingerprints.first } +} diff --git a/FlowCrypt/Models/Realm Models/ContactObject.swift b/FlowCrypt/Models/Realm Models/ContactObject.swift index ea1def172..4c4d313c5 100644 --- a/FlowCrypt/Models/Realm Models/ContactObject.swift +++ b/FlowCrypt/Models/Realm Models/ContactObject.swift @@ -1,5 +1,5 @@ // -// ContactObject.swift +// RecipientObject.swift // FlowCrypt // // Created by Anton Kharchevskyi on 21/08/2020. @@ -18,18 +18,18 @@ final class LongId: Object { } } -final class ContactObject: Object { +final class RecipientObject: Object { @Persisted(primaryKey: true) var email: String = "" @Persisted var name: String? @Persisted var lastUsed: Date? - @Persisted var pubKeys = List() + @Persisted var pubKeys = List() convenience init( email: String, name: String?, lastUsed: Date?, - keys: [ContactKey] + keys: [PubKey] ) { self.init() self.email = email @@ -37,25 +37,25 @@ final class ContactObject: Object { self.lastUsed = lastUsed keys - .map(ContactKeyObject.init) + .map(PubKeyObject.init) .forEach { self.pubKeys.append($0) } } } -extension ContactObject { - convenience init(_ contact: Contact) { +extension RecipientObject { + convenience init(_ recipient: RecipientWithPubKeys) { self.init( - email: contact.email, - name: contact.name, - lastUsed: contact.lastUsed, - keys: contact.pubKeys + email: recipient.email, + name: recipient.name, + lastUsed: recipient.lastUsed, + keys: recipient.pubKeys ) } } -extension ContactObject: CachedObject { +extension RecipientObject: CachedObject { // Contacts can be shared between accounts // https://github.com/FlowCrypt/flowcrypt-ios/issues/269 var activeUser: UserObject? { nil } From 7bd4d03569bba27b8ac9cab5c87b8b95183d0095 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Fri, 15 Oct 2021 23:13:51 +0300 Subject: [PATCH 10/10] issue #553 update tests --- .../LocalContactsProvider.swift | 2 +- .../Mocks/ContactsServiceMock.swift | 8 +++---- Gemfile.lock | 22 +++++++++---------- Podfile | 2 -- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift index ceb68cf9c..f36a367b2 100644 --- a/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Contacts Services/LocalContactsProvider.swift @@ -76,7 +76,7 @@ extension LocalContactsProvider: LocalContactsProviderType { func removePubKey(with fingerprint: String, for email: String) { find(with: email)? .pubKeys - .filter { $0.key == fingerprint } + .filter { $0.fingerprint == fingerprint } .forEach { key in try? localContactsCache.realm.write { localContactsCache.realm.delete(key) diff --git a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift index eb961e8a7..5d0ee9147 100644 --- a/FlowCryptAppTests/Mocks/ContactsServiceMock.swift +++ b/FlowCryptAppTests/Mocks/ContactsServiceMock.swift @@ -16,10 +16,10 @@ class ContactsServiceMock: ContactsServiceType { retrievePubKeysResult(email) } - var searchContactResult: Result! - func searchContact(with email: String) -> Promise { - Promise.resolveAfter(with: searchContactResult) + var searchContactResult: Result! + func searchContact(with email: String) -> Promise { + Promise.resolveAfter(with: searchContactResult) } - func remove(pubKey: String, for email: String) {} + func removePubKey(with fingerprint: String, for email: String) {} } diff --git a/Gemfile.lock b/Gemfile.lock index 8ad095581..5f04e7bd6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,13 +17,13 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.507.0) + aws-partitions (1.516.0) aws-sdk-core (3.121.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.48.0) + aws-sdk-kms (1.49.0) aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) aws-sdk-s3 (1.103.0) @@ -82,11 +82,11 @@ GEM domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) - emoji_regex (3.2.2) + emoji_regex (3.2.3) escape (0.0.4) - ethon (0.14.0) + ethon (0.15.0) ffi (>= 1.15.0) - excon (0.85.0) + excon (0.87.0) faraday (1.8.0) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -109,10 +109,10 @@ GEM faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) - faraday_middleware (1.1.0) + faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.5) - fastlane (2.195.0) + fastlane (2.196.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -156,7 +156,7 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.11.0) + google-apis-androidpublisher_v3 (0.12.0) google-apis-core (>= 0.4, < 2.a) google-apis-core (0.4.1) addressable (~> 2.5, >= 2.5.1) @@ -202,14 +202,14 @@ GEM i18n (1.8.10) concurrent-ruby (~> 1.0) jmespath (1.4.0) - json (2.5.1) - jwt (2.2.3) + json (2.6.0) + jwt (2.3.0) memoist (0.16.2) mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2021.0901) mini_magick (4.11.0) - mini_mime (1.1.1) + mini_mime (1.1.2) minitest (5.14.4) molinillo (0.8.0) multi_json (1.15.0) diff --git a/Podfile b/Podfile index 6e59ed2e8..b813e97e4 100644 --- a/Podfile +++ b/Podfile @@ -35,8 +35,6 @@ post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' - # config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO' - # config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' end end end