Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 26 additions & 35 deletions FlowCrypt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@
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 */; };
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 */; };
Expand Down Expand Up @@ -769,16 +769,16 @@
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 */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -2066,13 +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 */;
Expand Down Expand Up @@ -2183,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 = "";
Expand Down Expand Up @@ -3379,6 +3378,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;
Expand Down Expand Up @@ -3631,7 +3631,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";
Expand Down Expand Up @@ -3960,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";
Expand Down Expand Up @@ -4000,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";
Expand All @@ -4032,6 +4023,16 @@
package = 510A25FF26FDFEBE00163271 /* XCRemoteSwiftPackageReference "mailcore2" */;
productName = MailCore2;
};
512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */ = {
isa = XCSwiftPackageProductDependency;
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" */;
Expand All @@ -4057,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" */;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 35 additions & 16 deletions FlowCrypt/Controllers/Compose/ComposeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Never>()
private let cloudContactProvider: CloudContactsProvider
private let userDefaults: UserDefaults

Expand Down Expand Up @@ -96,10 +96,9 @@ 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() }
setupSearch()
}

deinit {
Expand Down Expand Up @@ -147,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 {
Expand Down Expand Up @@ -535,15 +552,13 @@ 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 {
search.send("")
updateState(with: .main)
return
}

search.send(text)
}

private func handleDidBeginEditing() {
Expand Down Expand Up @@ -667,7 +682,13 @@ extension ComposeViewController {
extension ComposeViewController {
private func updateState(with newState: State) {
state = newState
node.reloadSections(IndexSet(integer: 0), with: .fade)

switch state {
case .main:
node.reloadSections([0, 1], with: .fade)
case .searchEmails:
node.reloadSections([1], with: .fade)
}
}
}

Expand Down Expand Up @@ -797,8 +818,6 @@ extension ComposeViewController {
}
}

// temporary disable search contacts
// https://github.com/FlowCrypt/flowcrypt-ios/issues/217
extension ComposeViewController {
private func showScopeAlertIfNeeded() {
if shouldRenewToken(for: [.mail]),
Expand Down
2 changes: 1 addition & 1 deletion FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions FlowCrypt/Extensions/URLSessionExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +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 = "www.google.com"
static let searchPath = "/m8/feeds/contacts/default/thin"
}
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

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")
]
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 emails = contacts
.compactMap { $0.person?.emailAddresses }
.flatMap { $0 }
.compactMap { $0.value }

return Promise<[String]> { () -> [String] in
let response = try awaitPromise(URLSession.shared.call(URLRequest(url: url)))
let emails = try JSONDecoder().decode(GoogleContactsResponse.self, from: response.data).emails
return emails
resolve(emails)
}
}
}
}
Loading