diff --git a/Signal/src/ViewControllers/SignalsViewController.m b/Signal/src/ViewControllers/SignalsViewController.m index dc2b3d012..abcf37aa5 100644 --- a/Signal/src/ViewControllers/SignalsViewController.m +++ b/Signal/src/ViewControllers/SignalsViewController.m @@ -279,9 +279,18 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS - (IBAction)composeNew { MessageComposeTableViewController *viewController = [MessageComposeTableViewController new]; - UINavigationController *navigationController = - [[UINavigationController alloc] initWithRootViewController:viewController]; - [self presentTopLevelModalViewController:navigationController animateDismissal:YES animatePresentation:YES]; + + [self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) { + DDLogError(@"%@ Error when requesting contacts: %@", self.tag, error); + // Even if there was an error fetching contacts we proceed to the next screen. + // As the compose view will present the proper thing depending on contact access. + // + // We just want to make sure contact access is *complete* before showing the compose + // screen to avoid flicker. + UINavigationController *navigationController = + [[UINavigationController alloc] initWithRootViewController:viewController]; + [self presentTopLevelModalViewController:navigationController animateDismissal:YES animatePresentation:YES]; + }]; } - (void)swappedSegmentedControl { diff --git a/Signal/src/contact/OWSContactsManager.h b/Signal/src/contact/OWSContactsManager.h index 1de096163..5b6ba75f2 100644 --- a/Signal/src/contact/OWSContactsManager.h +++ b/Signal/src/contact/OWSContactsManager.h @@ -39,6 +39,7 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; // Request systems contacts and start syncing changes. The user will see an alert // if they haven't previously. - (void)requestSystemContactsOnce; +- (void)requestSystemContactsOnceWithCompletion:(void (^_Nullable)(NSError *_Nullable error))completion; // Ensure's the app has the latest contacts, but won't prompt the user for contact // access if they haven't granted it. diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index 8e5808bd7..fe3e68e20 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -54,9 +54,15 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification = // Request contacts access if you haven't asked recently. - (void)requestSystemContactsOnce { - [self.systemContactsFetcher requestOnce]; + [self requestSystemContactsOnceWithCompletion:nil]; } +- (void)requestSystemContactsOnceWithCompletion:(void (^_Nullable)(NSError *_Nullable error))completion +{ + [self.systemContactsFetcher requestOnceWithCompletion:completion]; +} + + - (void)fetchSystemContactsIfAlreadyAuthorized { [self.systemContactsFetcher fetchIfAlreadyAuthorized]; diff --git a/Signal/src/contact/SystemContactsFetcher.swift b/Signal/src/contact/SystemContactsFetcher.swift index 2b4794767..5775c2c23 100644 --- a/Signal/src/contact/SystemContactsFetcher.swift +++ b/Signal/src/contact/SystemContactsFetcher.swift @@ -39,11 +39,19 @@ class SystemContactsFetcher: NSObject { CNContactEmailAddressesKey as CNKeyDescriptor ] - public func requestOnce() { + /** + * Ensures we've requested access for system contacts. This can be used in multiple places, + * where we might need contact access, but will ensure we don't wastefully reload contacts + * if we have already fetched contacts. + * + * @param completion completion handler is called on main thread. + */ + public func requestOnce(completion: ((Error?) -> Void)?) { AssertIsOnMainThread() guard !systemContactsHaveBeenRequestedAtLeastOnce else { Logger.debug("\(TAG) already requested system contacts") + completion?(nil) return } systemContactsHaveBeenRequestedAtLeastOnce = true @@ -54,22 +62,32 @@ class SystemContactsFetcher: NSObject { contactStore.requestAccess(for: .contacts) { (granted, error) in if let error = error { Logger.error("\(self.TAG) error fetching contacts: \(error)") - assertionFailure() + DispatchQueue.main.async { + completion?(error) + } } guard granted else { Logger.info("\(self.TAG) declined contact access.") + // This case should have been caught be the error guard a few lines up. + assertionFailure() + DispatchQueue.main.async { + completion?(nil) + } return } DispatchQueue.main.async { - self.updateContacts() + self.updateContacts(completion: completion) } } case .authorized: - self.updateContacts() + self.updateContacts(completion: completion) case .denied, .restricted: Logger.debug("\(TAG) contacts were \(self.authorizationStatus)") + DispatchQueue.main.async { + completion?(nil) + } } } @@ -79,10 +97,10 @@ class SystemContactsFetcher: NSObject { return } - updateContacts() + updateContacts(completion: nil) } - private func updateContacts() { + private func updateContacts(completion: ((Error?) -> Void)?) { AssertIsOnMainThread() systemContactsHaveBeenRequestedAtLeastOnce = true @@ -100,11 +118,16 @@ class SystemContactsFetcher: NSObject { } catch let error as NSError { Logger.error("\(self.TAG) Failed to fetch contacts with error:\(error)") assertionFailure() + DispatchQueue.main.async { + completion?(error) + } + return } let contacts = systemContacts.map { Contact(systemContact: $0) } DispatchQueue.main.async { self.delegate?.systemContactsFetcher(self, updatedContacts: contacts) + completion?(nil) } } } @@ -118,7 +141,7 @@ class SystemContactsFetcher: NSObject { @objc private func contactStoreDidChange() { - updateContacts() + updateContacts(completion: nil) } }