From 5545b29f5a11610b9384632192030e68751632ad Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 6 Oct 2021 03:40:54 +0800 Subject: [PATCH 1/8] issue 283 reenable contact search --- .../Compose/ComposeViewController.swift | 23 ++++++++----------- .../Services/GeneralConstants.swift | 4 +--- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 1b5a4f007..36e834512 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -96,9 +96,7 @@ final class ComposeViewController: TableNodeViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - - // temporary disable search contacts - https://github.com/FlowCrypt/flowcrypt-ios/issues/217 - // showScopeAlertIfNeeded() + showScopeAlertIfNeeded() cancellable.forEach { $0.cancel() } } @@ -527,15 +525,14 @@ extension ComposeViewController { } private func handleEditingChanged(with text: String?) { -// temporary disable search contacts - https://github.com/FlowCrypt/flowcrypt-ios/issues/217 -// guard let text = text, text.isNotEmpty else { -// updateState(with: .main) -// return -// } -// -// searchThrottler.throttle { [weak self] in -// self?.searchEmail(with: text) -// } + guard let text = text, text.isNotEmpty else { + updateState(with: .main) + return + } + + searchThrottler.throttle { [weak self] in + self?.searchEmail(with: text) + } } private func handleDidBeginEditing() { @@ -789,8 +786,6 @@ extension ComposeViewController { } } -// temporary disable search contacts -// https://github.com/FlowCrypt/flowcrypt-ios/issues/217 extension ComposeViewController { private func showScopeAlertIfNeeded() { if shouldRenewToken(for: [.mail]), diff --git a/FlowCrypt/Functionality/Services/GeneralConstants.swift b/FlowCrypt/Functionality/Services/GeneralConstants.swift index 4da5daf99..cd078afb1 100644 --- a/FlowCrypt/Functionality/Services/GeneralConstants.swift +++ b/FlowCrypt/Functionality/Services/GeneralConstants.swift @@ -8,9 +8,7 @@ enum GeneralConstants { enum Gmail { static let clientID = "679326713487-8f07eqt1hvjvopgcjeie4dbtni4ig0rc.apps.googleusercontent.com" static let redirectURL = URL(string: "com.googleusercontent.apps.679326713487-8f07eqt1hvjvopgcjeie4dbtni4ig0rc:/oauthredirect")! - - // temporary disable search contacts - https://github.com/FlowCrypt/flowcrypt-ios/issues/217 - static let currentScope: [GoogleScope] = [.mail, .userInfo] + static let currentScope: [GoogleScope] = GoogleScope.allCases } enum Global { From e1df8d70d049fe40aca5adaea09de69440ea9f95 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 6 Oct 2021 15:47:26 +0300 Subject: [PATCH 2/8] issue #283 remove token from contacts request log --- FlowCrypt/Extensions/URLSessionExtension.swift | 2 +- FlowCryptCommon/Extensions/URLExtension.swift | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/FlowCrypt/Extensions/URLSessionExtension.swift b/FlowCrypt/Extensions/URLSessionExtension.swift index df0f1fbcf..aca78f119 100644 --- a/FlowCrypt/Extensions/URLSessionExtension.swift +++ b/FlowCrypt/Extensions/URLSessionExtension.swift @@ -27,7 +27,7 @@ extension URLSession { let res = response as? HTTPURLResponse let status = res?.statusCode ?? GeneralConstants.Global.generalError let urlMethod = urlRequest.httpMethod ?? "GET" - let urlString = urlRequest.url?.absoluteString ?? "??" + let urlString = urlRequest.url?.stringWithFilteredTokens ?? "??" let headers = urlRequest.headersWithFilteredTokens let message = "URLSession.call status:\(status) ms:\(trace.finish()) \(urlMethod) \(urlString), headers: \(headers)" Logger.nested("URLSession").logInfo(message) diff --git a/FlowCryptCommon/Extensions/URLExtension.swift b/FlowCryptCommon/Extensions/URLExtension.swift index cf1c6af8f..ce77a834d 100644 --- a/FlowCryptCommon/Extensions/URLExtension.swift +++ b/FlowCryptCommon/Extensions/URLExtension.swift @@ -26,6 +26,18 @@ public extension URL { var mimeType: String { return Self.mimeTypes[pathExtension] ?? "text/plain" } + + var stringWithFilteredTokens: String { + guard var components = URLComponents(string: absoluteString), + let queryItems = components.queryItems + else { return absoluteString } + + components.queryItems = queryItems.map { + URLQueryItem(name: $0.name, value: $0.name.contains("token") ? "***" : $0.value) + } + + return components.url?.absoluteString ?? absoluteString + } } public extension String { From 2692819352c4053b3e670b3060b0a3e8cdf8eee2 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 6 Oct 2021 16:38:19 +0300 Subject: [PATCH 3/8] issue #283 fix contacts search crash --- FlowCrypt/Controllers/Compose/ComposeViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 58fea6af9..e15076c44 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -660,7 +660,7 @@ extension ComposeViewController { extension ComposeViewController { private func updateState(with newState: State) { state = newState - node.reloadSections(IndexSet(integer: 0), with: .fade) + node.reloadSections([0, 1], with: .fade) } } From 421db77dbd3ba9a46a30ca0c67f7c8148c8f2ee3 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 7 Oct 2021 15:12:14 +0300 Subject: [PATCH 4/8] issue #283 use People API for contacts search --- .../Extensions/URLSessionExtension.swift | 4 +- .../CloudContactsProvider.swift | 18 +++---- .../GoogleContactsResponse.swift | 47 ++++++------------- Podfile.lock | 18 +++---- 4 files changed, 36 insertions(+), 51 deletions(-) diff --git a/FlowCrypt/Extensions/URLSessionExtension.swift b/FlowCrypt/Extensions/URLSessionExtension.swift index aca78f119..6e909393c 100644 --- a/FlowCrypt/Extensions/URLSessionExtension.swift +++ b/FlowCrypt/Extensions/URLSessionExtension.swift @@ -70,8 +70,8 @@ struct URLHeader { extension URLRequest { static func urlRequest( with urlString: String, - method: HTTPMetod, - body: Data?, + method: HTTPMetod = .get, + body: Data? = nil, headers: [URLHeader] = [] ) -> URLRequest { guard let url = URL(string: urlString) else { diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift index b3fd6fa0b..f4796ddd2 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift @@ -16,8 +16,8 @@ protocol CloudContactsProvider { final class UserContactsProvider { private enum Constants { static let scheme = "https" - static let host = "www.google.com" - static let searchPath = "/m8/feeds/contacts/default/thin" + static let host = "people.googleapis.com" + static let searchPath = "/v1/people:searchContacts" } private let dataService: DataService @@ -46,13 +46,11 @@ extension UserContactsProvider: CloudContactsProvider { return Promise(AppErr.unexpected("Missing token")) } + // TODO: Add warmup query var searchComponents = components(for: Constants.searchPath) searchComponents.queryItems = [ - URLQueryItem(name: "q", value: query), - URLQueryItem(name: "v", value: "3.0"), - URLQueryItem(name: "alt", value: "json"), - URLQueryItem(name: "access_token", value: token), - URLQueryItem(name: "start-index", value: "0") + URLQueryItem(name: "query", value: query), + URLQueryItem(name: "readMask", value: "names,emailAddresses") ] guard let url = searchComponents.url else { @@ -60,8 +58,12 @@ extension UserContactsProvider: CloudContactsProvider { return Promise(AppErr.unexpected("Missing url")) } + let headers = [URLHeader(value: "Bearer \(token)", httpHeaderField: "Authorization"), + URLHeader(value: "application/json; charset=UTF-8", httpHeaderField: "Content-type")] + let request = URLRequest.urlRequest(with: url.absoluteString, headers: headers) + return Promise<[String]> { () -> [String] in - let response = try awaitPromise(URLSession.shared.call(URLRequest(url: url))) + let response = try awaitPromise(URLSession.shared.call(request)) let emails = try JSONDecoder().decode(GoogleContactsResponse.self, from: response.data).emails return emails } diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/GoogleContactsResponse.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/GoogleContactsResponse.swift index b4680294b..6b06cde3f 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/GoogleContactsResponse.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/GoogleContactsResponse.swift @@ -8,45 +8,28 @@ import Foundation -// swiftlint:disable nesting struct GoogleContactsResponse: Decodable { - let emails: [String] + let results: [PersonResult]? - private enum CodingKeys: String, CodingKey { - case feed + struct PersonResult: Decodable { + let person: Person - enum Entry: String, CodingKey { - case entry + struct Person: Decodable { + let emailAddresses: [EmailAddress] - enum AdressKeys: String, CodingKey { - case email = "gd$email" - - enum EmailKeys: String, CodingKey { - case address - } + struct EmailAddress: Decodable { + let value: String } } } +} - init(from decoder: Decoder) throws { - // top-level container - let container = try decoder.container(keyedBy: CodingKeys.self) - - do { - let feedContainer = try container.nestedContainer(keyedBy: CodingKeys.Entry.self, forKey: .feed) - - var entryContainer = try feedContainer.nestedUnkeyedContainer(forKey: .entry) - - var names = [String]() - while !entryContainer.isAtEnd { - let addressContainer = try entryContainer.nestedContainer(keyedBy: CodingKeys.Entry.AdressKeys.self) - var emailContainer = try addressContainer.nestedUnkeyedContainer(forKey: .email) - let resultContainer = try emailContainer.nestedContainer(keyedBy: CodingKeys.Entry.AdressKeys.EmailKeys.self) - names.append(try resultContainer.decode(String.self, forKey: .address)) - } - emails = names - } catch { - emails = [] - } +extension GoogleContactsResponse { + var emails: [String] { + results? + .map(\.person) + .flatMap(\.emailAddresses) + .map { String($0.value) } + ?? [] } } diff --git a/Podfile.lock b/Podfile.lock index bc9dc7a8c..2be426c37 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -43,12 +43,12 @@ PODS: - PromisesObjC (2.0.0) - PromisesSwift (2.0.0): - PromisesObjC (= 2.0.0) - - Realm (10.16.0): - - Realm/Headers (= 10.16.0) - - Realm/Headers (10.16.0) - - RealmSwift (10.16.0): - - Realm (= 10.16.0) - - SwiftFormat/CLI (0.48.13) + - Realm (10.17.0): + - Realm/Headers (= 10.17.0) + - Realm/Headers (10.17.0) + - RealmSwift (10.17.0): + - Realm (= 10.17.0) + - SwiftFormat/CLI (0.48.14) - SwiftLint (0.44.0) - SwiftyRSA (1.7.0): - SwiftyRSA/ObjC (= 1.7.0) @@ -130,9 +130,9 @@ SPEC CHECKSUMS: PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01 PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 PromisesSwift: e0b2a6433469efb0b83a2b84c62a2abab8e5e5d4 - Realm: b6027801398f3743fc222f096faa85281b506e6c - RealmSwift: b02a3d0e4947955da960b642c98ad3a461fc4e70 - SwiftFormat: 0c4b53c1acb503e2a743ecd9a18f51958d41d40a + Realm: 9533276bd76f66aa2fd0d3ffe700c1f6023dcab6 + RealmSwift: 57f5d59aa29852587aad1e861989e7af139894b5 + SwiftFormat: 45eb1675b89bee034a18693dfc550bd5c98d1b2d SwiftLint: e96c0a8c770c7ebbc4d36c55baf9096bb65c4584 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3 From d7ab17955cf33ba1207dd83a1a3570f442a6e38b Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Fri, 8 Oct 2021 23:49:24 +0300 Subject: [PATCH 5/8] issue #283 use people API for email search --- FlowCrypt.xcodeproj/project.pbxproj | 10 ++- .../CloudContactsProvider.swift | 84 ++++++++++--------- 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index d5cc25c4b..bc7f62348 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ 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 */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; 51E13F12270F92BA00F287CA /* IDZSwiftCommonCrypto in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */; }; @@ -779,6 +780,7 @@ 51E13F18270F934C00F287CA /* MBProgressHUD in Frameworks */, 5298EA408FEC36021F7558BD /* Pods_FlowCrypt.framework in Frameworks */, 51E1675F270F36A400D27C52 /* RealmSwift in Frameworks */, + 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */, 51E16749270F303100D27C52 /* GoogleSignIn in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2073,6 +2075,7 @@ 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */, 51E13F14270F92F200F287CA /* BigInt */, 51E13F17270F934C00F287CA /* MBProgressHUD */, + 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */, ); productName = FlowCrypt; productReference = C132B9B01EC2DBD800763715 /* FlowCrypt.app */; @@ -3379,6 +3382,7 @@ ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -3631,7 +3635,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FlowCryptUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4032,6 +4035,11 @@ package = 510A25FF26FDFEBE00163271 /* XCRemoteSwiftPackageReference "mailcore2" */; productName = MailCore2; }; + 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */ = { + isa = XCSwiftPackageProductDependency; + package = 51E16744270F301F00D27C52 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */; + productName = GoogleAPIClientForREST_PeopleService; + }; 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */ = { isa = XCSwiftPackageProductDependency; package = 51E13F10270F92BA00F287CA /* XCRemoteSwiftPackageReference "IDZSwiftCommonCrypto" */; diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift index f4796ddd2..036c130be 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift @@ -6,66 +6,70 @@ // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // -import Foundation +import FlowCryptCommon import Promises +import GoogleAPIClientForREST_PeopleService protocol CloudContactsProvider { func searchContacts(query: String) -> Promise<[String]> } -final class UserContactsProvider { - private enum Constants { - static let scheme = "https" - static let host = "people.googleapis.com" - static let searchPath = "/v1/people:searchContacts" - } +enum CloudContactsProviderError: Error { + /// People API response parsing + case failedToParseData(Any?) + /// Provider Error + case providerError(Error) +} - private let dataService: DataService +final class UserContactsProvider { + private let logger = Logger.nested("UserContactsProvider") + private let userService: GoogleUserServiceType + private var peopleService: GTLRPeopleServiceService { + let service = GTLRPeopleServiceService() - private var token: String? { - dataService.token - } + if userService.authorization == nil { + logger.logWarning("authorization for current user is nil") + } - init(dataService: DataService = .shared) { - self.dataService = dataService + service.authorizer = userService.authorization + return service } - private func components(for path: String) -> URLComponents { - var components = URLComponents() - components.scheme = Constants.scheme - components.host = Constants.host - components.path = path - return components + init(userService: GoogleUserServiceType = GoogleUserService()) { + self.userService = userService + + // Warmup query for contacts cache + _ = self.searchContacts(query: "") } } extension UserContactsProvider: CloudContactsProvider { func searchContacts(query: String) -> Promise<[String]> { - guard let token = token else { - assertionFailure("token should not be nil") - return Promise(AppErr.unexpected("Missing token")) - } + let searchQuery = GTLRPeopleServiceQuery_PeopleSearchContacts.query() + searchQuery.readMask = "names,emailAddresses" + searchQuery.query = query - // TODO: Add warmup query - var searchComponents = components(for: Constants.searchPath) - searchComponents.queryItems = [ - URLQueryItem(name: "query", value: query), - URLQueryItem(name: "readMask", value: "names,emailAddresses") - ] + return Promise<[String]> { resolve, reject in + self.peopleService.executeQuery(searchQuery) { _, data, error in + if let error = error { + return reject(CloudContactsProviderError.providerError(error)) + } - guard let url = searchComponents.url else { - assertionFailure("Url should not be nil") - return Promise(AppErr.unexpected("Missing url")) - } + guard let response = data as? GTLRPeopleService_SearchResponse else { + return reject(AppErr.cast("GTLRPeopleService_SearchResponse")) + } + + guard let contacts = response.results else { + return reject(CloudContactsProviderError.failedToParseData(data)) + } - let headers = [URLHeader(value: "Bearer \(token)", httpHeaderField: "Authorization"), - URLHeader(value: "application/json; charset=UTF-8", httpHeaderField: "Content-type")] - let request = URLRequest.urlRequest(with: url.absoluteString, headers: headers) + let emails = contacts + .compactMap { $0.person?.emailAddresses } + .flatMap { $0 } + .compactMap { $0.value } - return Promise<[String]> { () -> [String] in - let response = try awaitPromise(URLSession.shared.call(request)) - let emails = try JSONDecoder().decode(GoogleContactsResponse.self, from: response.data).emails - return emails + resolve(emails) + } } } } From 2d33f3698c26c00012d56e684906c6411137baa1 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Sun, 10 Oct 2021 23:43:15 +0300 Subject: [PATCH 6/8] issue #283 debounce contacts search requests --- FlowCrypt.xcodeproj/project.pbxproj | 51 +++++++------------ .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Compose/ComposeViewController.swift | 34 +++++++++++-- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index bc7f62348..f76f43668 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -56,14 +56,13 @@ 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 */; }; 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 */; }; 51E1673D270DAFF900D27C52 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 51E1673C270DAFF900D27C52 /* Localizable.stringsdict */; }; 51E16746270F301F00D27C52 /* GoogleAPIClientForREST_Gmail in Frameworks */ = {isa = PBXBuildFile; productRef = 51E16745270F301F00D27C52 /* GoogleAPIClientForREST_Gmail */; }; 51E16749270F303100D27C52 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 51E16748270F303100D27C52 /* GoogleSignIn */; }; - 51E1674F270F341300D27C52 /* Promises in Frameworks */ = {isa = PBXBuildFile; productRef = 51E1674E270F341300D27C52 /* Promises */; }; - 51E16755270F356500D27C52 /* GTMAppAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 51E16754270F356500D27C52 /* GTMAppAuth */; }; 51E1675D270F36A400D27C52 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 51E1675C270F36A400D27C52 /* Realm */; }; 51E1675F270F36A400D27C52 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 51E1675E270F36A400D27C52 /* RealmSwift */; }; 5298EA408FEC36021F7558BD /* Pods_FlowCrypt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4753E9A27694B4D34C980FFA /* Pods_FlowCrypt.framework */; }; @@ -770,16 +769,15 @@ files = ( D2CDC3E224052D54002B045F /* FlowCryptCommon.framework in Frameworks */, D2CDC3DD24052D50002B045F /* FlowCryptUI.framework in Frameworks */, - 51E1674F270F341300D27C52 /* Promises in Frameworks */, 51E13F15270F92F200F287CA /* BigInt in Frameworks */, 51E16746270F301F00D27C52 /* GoogleAPIClientForREST_Gmail in Frameworks */, 510A260126FDFEBE00163271 /* MailCore2 in Frameworks */, - 51E16755270F356500D27C52 /* GTMAppAuth in Frameworks */, 51E1675D270F36A400D27C52 /* Realm in Frameworks */, 51E13F12270F92BA00F287CA /* IDZSwiftCommonCrypto in Frameworks */, 51E13F18270F934C00F287CA /* MBProgressHUD in Frameworks */, 5298EA408FEC36021F7558BD /* Pods_FlowCrypt.framework in Frameworks */, 51E1675F270F36A400D27C52 /* RealmSwift in Frameworks */, + 51B4AE4F27137CB70001F33B /* Promises in Frameworks */, 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */, 51E16749270F303100D27C52 /* GoogleSignIn in Frameworks */, ); @@ -2068,14 +2066,13 @@ 510A260026FDFEBE00163271 /* MailCore2 */, 51E16745270F301F00D27C52 /* GoogleAPIClientForREST_Gmail */, 51E16748270F303100D27C52 /* GoogleSignIn */, - 51E1674E270F341300D27C52 /* Promises */, - 51E16754270F356500D27C52 /* GTMAppAuth */, 51E1675C270F36A400D27C52 /* Realm */, 51E1675E270F36A400D27C52 /* RealmSwift */, 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */, 51E13F14270F92F200F287CA /* BigInt */, 51E13F17270F934C00F287CA /* MBProgressHUD */, 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */, + 51B4AE4E27137CB70001F33B /* Promises */, ); productName = FlowCrypt; productReference = C132B9B01EC2DBD800763715 /* FlowCrypt.app */; @@ -2186,12 +2183,11 @@ 510A25FF26FDFEBE00163271 /* XCRemoteSwiftPackageReference "mailcore2" */, 51E16744270F301F00D27C52 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */, 51E16747270F303100D27C52 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, - 51E1674D270F341200D27C52 /* XCRemoteSwiftPackageReference "promises" */, - 51E16753270F356500D27C52 /* XCRemoteSwiftPackageReference "GTMAppAuth" */, 51E1675B270F36A400D27C52 /* XCRemoteSwiftPackageReference "realm-cocoa" */, 51E13F10270F92BA00F287CA /* XCRemoteSwiftPackageReference "IDZSwiftCommonCrypto" */, 51E13F13270F92F100F287CA /* XCRemoteSwiftPackageReference "BigInt" */, 51E13F16270F934C00F287CA /* XCRemoteSwiftPackageReference "MBProgressHUD" */, + 51B4AE4D27137CB70001F33B /* XCRemoteSwiftPackageReference "promises" */, ); productRefGroup = C132B9B11EC2DBD800763715 /* Products */; projectDirPath = ""; @@ -3963,6 +3959,14 @@ kind = branch; }; }; + 51B4AE4D27137CB70001F33B /* XCRemoteSwiftPackageReference "promises" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/google/promises"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; 51E13F10270F92BA00F287CA /* XCRemoteSwiftPackageReference "IDZSwiftCommonCrypto" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/iosdevzone/IDZSwiftCommonCrypto"; @@ -4003,22 +4007,6 @@ minimumVersion = 6.0.0; }; }; - 51E1674D270F341200D27C52 /* XCRemoteSwiftPackageReference "promises" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/google/promises"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; - }; - }; - 51E16753270F356500D27C52 /* XCRemoteSwiftPackageReference "GTMAppAuth" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/google/GTMAppAuth"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; 51E1675B270F36A400D27C52 /* XCRemoteSwiftPackageReference "realm-cocoa" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/realm-cocoa"; @@ -4040,6 +4028,11 @@ package = 51E16744270F301F00D27C52 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */; productName = GoogleAPIClientForREST_PeopleService; }; + 51B4AE4E27137CB70001F33B /* Promises */ = { + isa = XCSwiftPackageProductDependency; + package = 51B4AE4D27137CB70001F33B /* XCRemoteSwiftPackageReference "promises" */; + productName = Promises; + }; 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */ = { isa = XCSwiftPackageProductDependency; package = 51E13F10270F92BA00F287CA /* XCRemoteSwiftPackageReference "IDZSwiftCommonCrypto" */; @@ -4065,16 +4058,6 @@ package = 51E16747270F303100D27C52 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; productName = GoogleSignIn; }; - 51E1674E270F341300D27C52 /* Promises */ = { - isa = XCSwiftPackageProductDependency; - package = 51E1674D270F341200D27C52 /* XCRemoteSwiftPackageReference "promises" */; - productName = Promises; - }; - 51E16754270F356500D27C52 /* GTMAppAuth */ = { - isa = XCSwiftPackageProductDependency; - package = 51E16753270F356500D27C52 /* XCRemoteSwiftPackageReference "GTMAppAuth" */; - productName = GTMAppAuth; - }; 51E1675C270F36A400D27C52 /* Realm */ = { isa = XCSwiftPackageProductDependency; package = 51E1675B270F36A400D27C52 /* XCRemoteSwiftPackageReference "realm-cocoa" */; diff --git a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8d66041a3..44d4ba74b 100644 --- a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -48,7 +48,7 @@ }, { "package": "GTMAppAuth", - "repositoryURL": "https://github.com/google/GTMAppAuth", + "repositoryURL": "https://github.com/google/GTMAppAuth.git", "state": { "branch": null, "revision": "40f4103fb52109032c05599a0c39ad43edbdf80a", diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 52189152a..04d0412fb 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -37,7 +37,7 @@ final class ComposeViewController: TableNodeViewController { private let filesManager: FilesManagerType private let photosManager: PhotosManagerType - private let searchThrottler = Throttler(seconds: 1) + private let search = PassthroughSubject() private let cloudContactProvider: CloudContactsProvider private let userDefaults: UserDefaults @@ -98,6 +98,7 @@ final class ComposeViewController: TableNodeViewController { super.viewDidAppear(animated) showScopeAlertIfNeeded() cancellable.forEach { $0.cancel() } + setupSearch() } deinit { @@ -145,6 +146,24 @@ extension ComposeViewController { } } +// MARK: - Search +extension ComposeViewController { + private func setupSearch() { + search + .debounce(for: .milliseconds(400), scheduler: RunLoop.main) + .removeDuplicates() + .compactMap { [weak self] in + guard $0.isNotEmpty else { + self?.updateState(with: .main) + return nil + } + return $0 + } + .sink { [weak self] in self?.searchEmail(with: $0) } + .store(in: &cancellable) + } +} + // MARK: - Keyboard extension ComposeViewController { @@ -534,13 +553,12 @@ extension ComposeViewController { private func handleEditingChanged(with text: String?) { guard let text = text, text.isNotEmpty else { + search.send("") updateState(with: .main) return } - searchThrottler.throttle { [weak self] in - self?.searchEmail(with: text) - } + search.send(text) } private func handleDidBeginEditing() { @@ -664,7 +682,13 @@ extension ComposeViewController { extension ComposeViewController { private func updateState(with newState: State) { state = newState - node.reloadSections([0, 1], with: .fade) + + switch state { + case .main: + node.reloadSections([0, 1], with: .fade) + case .searchEmails: + node.reloadSections([1], with: .fade) + } } } From c2b306069e9a4f566369f2095977d7babb13aa23 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Sun, 10 Oct 2021 23:50:45 +0300 Subject: [PATCH 7/8] issue #283 fix dark theme search results --- FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift index bab03ba07..f725c65c0 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift @@ -57,7 +57,7 @@ struct ComposeViewDecorator { InfoCellNode.Input( attributedText: email.attributed( .medium(17), - color: UIColor.black.withAlphaComponent(0.8), + color: UIColor.mainTextColor.withAlphaComponent(0.8), alignment: .left ), image: nil, From ad7e15aabd51d2a6ed632610b5307ebecbec5365 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 11 Oct 2021 12:05:26 +0300 Subject: [PATCH 8/8] issue #283 update testGmailConstants --- FlowCryptAppTests/GeneralConstantsTest.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FlowCryptAppTests/GeneralConstantsTest.swift b/FlowCryptAppTests/GeneralConstantsTest.swift index 2a7c9b557..b0447cd59 100644 --- a/FlowCryptAppTests/GeneralConstantsTest.swift +++ b/FlowCryptAppTests/GeneralConstantsTest.swift @@ -28,10 +28,10 @@ class GeneralConstantsTest: XCTestCase { let currentScope: Set = Set(GeneralConstants.Gmail.currentScope.map { $0.value }) let expectedScope = Set([ "https://www.googleapis.com/auth/userinfo.profile", - "https://mail.google.com/" + "https://mail.google.com/", + "https://www.googleapis.com/auth/contacts.readonly" ]) XCTAssert(currentScope == expectedScope) - XCTAssertFalse(currentScope.contains("https://www.googleapis.com/auth/contacts.readonly")) // Client Id let clientId = GeneralConstants.Gmail.clientID