Merge branch 'mkirk/missing-contacts-compose'

pull/1/head
Michael Kirk 8 years ago
commit bfc4ff6f05

@ -145,20 +145,27 @@ open class ContactsPicker: UIViewController, UITableViewDelegate, UITableViewDat
func getContacts(onError errorHandler: @escaping (_ error: Error) -> Void) {
switch CNContactStore.authorizationStatus(for: CNEntityType.contacts) {
case CNAuthorizationStatus.denied, CNAuthorizationStatus.restricted:
let title = NSLocalizedString("INVITE_FLOW_REQUIRES_CONTACT_ACCESS_TITLE", comment: "Alert title when contacts disabled while trying to invite contacts to signal")
let body = NSLocalizedString("INVITE_FLOW_REQUIRES_CONTACT_ACCESS_BODY", comment: "Alert body when contacts disabled while trying to invite contacts to signal")
let title = NSLocalizedString("AB_PERMISSION_MISSING_TITLE", comment: "Alert title when contacts disabled")
let body = NSLocalizedString("ADDRESSBOOK_RESTRICTED_ALERT_BODY", comment: "Alert body when contacts disabled")
let alert = UIAlertController(title: title, message: body, preferredStyle: UIAlertControllerStyle.alert)
let dismissText = NSLocalizedString("DISMISS_BUTTON_TEXT", comment:"")
let dismissText = NSLocalizedString("TXT_CANCEL_TITLE", comment:"")
let okAction = UIAlertAction(title: dismissText, style: UIAlertActionStyle.default, handler: { _ in
let cancelAction = UIAlertAction(title: dismissText, style: .cancel, handler: { _ in
let error = NSError(domain: "contactsPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"])
self.contactsPickerDelegate?.contactsPicker(self, didContactFetchFailed: error)
errorHandler(error)
self.dismiss(animated: true, completion: nil)
})
alert.addAction(okAction)
alert.addAction(cancelAction)
let settingsText = NSLocalizedString("OPEN_SETTINGS_BUTTON", comment:"Button text which opens the settings app")
let openSettingsAction = UIAlertAction(title: settingsText, style: .default, handler: { (_) in
UIApplication.shared.openSystemSettings()
})
alert.addAction(openSettingsAction)
self.present(alert, animated: true, completion: nil)
case CNAuthorizationStatus.notDetermined:
@ -208,6 +215,10 @@ open class ContactsPicker: UIViewController, UITableViewDelegate, UITableViewDat
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let dataSource = filteredSections
guard section < dataSource.count else {
return 0
}
return dataSource[section].count
}
@ -274,7 +285,15 @@ open class ContactsPicker: UIViewController, UITableViewDelegate, UITableViewDat
open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let dataSource = filteredSections
guard section < dataSource.count else {
return nil
}
if dataSource[section].count > 0 {
guard section < collation.sectionTitles.count else {
return nil
}
return collation.sectionTitles[section]
} else {
return nil

@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@property (nonatomic, readonly) UISearchBar *searchBar;
@property (nonatomic, readonly) NSLayoutConstraint *hideContactsPermissionReminderViewConstraint;
// A list of possible phone numbers parsed from the search text as
// E164 values.
@ -59,6 +60,18 @@ NS_ASSUME_NONNULL_BEGIN
_contactsViewHelper.delegate = self;
_nonContactAccountSet = [NSMutableSet set];
ReminderView *contactsPermissionReminderView = [[ReminderView alloc]
initWithText:NSLocalizedString(@"COMPOSE_SCREEN_MISSING_CONTACTS_PERMISSION",
@"Multiline label explaining why compose-screen contact picker is empty.")
tapAction:^{
[[UIApplication sharedApplication] openSystemSettings];
}];
[self.view addSubview:contactsPermissionReminderView];
[contactsPermissionReminderView autoPinWidthToSuperview];
[contactsPermissionReminderView autoPinEdgeToSuperviewMargin:ALEdgeTop];
_hideContactsPermissionReminderViewConstraint =
[contactsPermissionReminderView autoSetDimension:ALDimensionHeight toSize:0];
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
@ -87,7 +100,8 @@ NS_ASSUME_NONNULL_BEGIN
_tableViewController.tableViewStyle = UITableViewStylePlain;
[self.view addSubview:self.tableViewController.view];
[_tableViewController.view autoPinWidthToSuperview];
[_tableViewController.view autoPinEdgeToSuperviewEdge:ALEdgeTop];
[_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:contactsPermissionReminderView];
[_tableViewController.view autoPinToBottomLayoutGuideOfViewController:self withInset:0];
_tableViewController.tableView.tableHeaderView = searchBar;
@ -101,6 +115,20 @@ NS_ASSUME_NONNULL_BEGIN
[self updateTableContents];
}
- (void)showContactsPermissionReminder:(BOOL)isVisible
{
_hideContactsPermissionReminderViewConstraint.active = !isVisible;
}
- (void)showSearchBar:(BOOL)isVisible
{
if (isVisible) {
self.tableViewController.tableView.tableHeaderView = self.searchBar;
} else {
self.tableViewController.tableView.tableHeaderView = nil;
}
}
- (UIView *)createNoSignalContactsView
{
UIView *view = [UIView new];
@ -151,7 +179,7 @@ NS_ASSUME_NONNULL_BEGIN
UIButton *inviteContactsButton = [UIButton buttonWithType:UIButtonTypeCustom];
[inviteContactsButton setTitle:NSLocalizedString(@"INVITE_FRIENDS_CONTACT_TABLE_BUTTON",
"Text for button at the top of the contact picker")
"Label for the cell that presents the 'invite contacts' workflow.")
forState:UIControlStateNormal];
[inviteContactsButton setTitleColor:[UIColor ows_materialBlueColor] forState:UIControlStateNormal];
[inviteContactsButton.titleLabel setFont:[UIFont ows_regularFontWithSize:17.f]];
@ -199,7 +227,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.navigationController.navigationBar setTranslucent:NO];
[self showNoContactsModeIfNecessary];
[self showContactAppropriateViews];
}
#pragma mark - Table Contents
@ -240,20 +268,22 @@ NS_ASSUME_NONNULL_BEGIN
[weakSelf.navigationController pushViewController:viewController animated:YES];
}]];
// Invite Contacts
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
cell.textLabel.text = NSLocalizedString(@"INVITE_FRIENDS_CONTACT_TABLE_BUTTON",
@"Label for the cell that presents the 'invite contacts' workflow.");
cell.textLabel.font = [UIFont ows_regularFontWithSize:18.f];
cell.textLabel.textColor = [UIColor blackColor];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
if (self.contactsViewHelper.contactsManager.isSystemContactsAuthorized) {
// Invite Contacts
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
cell.textLabel.text = NSLocalizedString(@"INVITE_FRIENDS_CONTACT_TABLE_BUTTON",
@"Label for the cell that presents the 'invite contacts' workflow.");
cell.textLabel.font = [UIFont ows_regularFontWithSize:18.f];
cell.textLabel.textColor = [UIColor blackColor];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
customRowHeight:kActionCellHeight
actionBlock:^{
[weakSelf presentInviteFlow];
}]];
}
customRowHeight:kActionCellHeight
actionBlock:^{
[weakSelf presentInviteFlow];
}]];
// If the search string looks like a phone number, show either "new conversation..." cells and/or
// "invite via SMS..." cells.
@ -357,18 +387,20 @@ NS_ASSUME_NONNULL_BEGIN
if (!hasSearchText && helper.signalAccounts.count < 1) {
// No Contacts
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
cell.textLabel.text = NSLocalizedString(
@"SETTINGS_BLOCK_LIST_NO_CONTACTS", @"A label that indicates the user has no Signal contacts.");
cell.textLabel.font = [UIFont ows_regularFontWithSize:15.f];
cell.textLabel.textColor = [UIColor colorWithWhite:0.5f alpha:1.f];
cell.textLabel.textAlignment = NSTextAlignmentCenter;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
if (self.contactsViewHelper.contactsManager.isSystemContactsAuthorized) {
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
cell.textLabel.text = NSLocalizedString(
@"SETTINGS_BLOCK_LIST_NO_CONTACTS", @"A label that indicates the user has no Signal contacts.");
cell.textLabel.font = [UIFont ows_regularFontWithSize:15.f];
cell.textLabel.textColor = [UIColor colorWithWhite:0.5f alpha:1.f];
cell.textLabel.textAlignment = NSTextAlignmentCenter;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
customRowHeight:kActionCellHeight
actionBlock:nil]];
}
customRowHeight:kActionCellHeight
actionBlock:nil]];
}
if (hasSearchText && !hasSearchResults) {
@ -407,7 +439,7 @@ NS_ASSUME_NONNULL_BEGIN
{
[[Environment preferences] setHasDeclinedNoContactsView:YES];
[self showNoContactsModeIfNecessary];
[self showContactAppropriateViews];
}
- (void)presentInviteFlow
@ -418,10 +450,20 @@ NS_ASSUME_NONNULL_BEGIN
[self presentViewController:inviteFlow.actionSheetController animated:YES completion:nil];
}
- (void)showNoContactsModeIfNecessary
- (void)showContactAppropriateViews
{
BOOL hasNoContacts = self.contactsViewHelper.signalAccounts.count < 1;
self.isNoContactsModeActive = (hasNoContacts && ![[Environment preferences] hasDeclinedNoContactsView]);
if (self.contactsViewHelper.contactsManager.isSystemContactsAuthorized) {
BOOL hasNoContacts = self.contactsViewHelper.signalAccounts.count < 1;
self.isNoContactsModeActive = (hasNoContacts && ![[Environment preferences] hasDeclinedNoContactsView]);
[self showContactsPermissionReminder:NO];
[self showSearchBar:YES];
} else {
// don't show "no signal contacts", show "no contact access"
self.isNoContactsModeActive = NO;
[self showContactsPermissionReminder:YES];
[self showSearchBar:NO];
}
}
- (void)setIsNoContactsModeActive:(BOOL)isNoContactsModeActive
@ -579,7 +621,7 @@ NS_ASSUME_NONNULL_BEGIN
{
[self updateTableContents];
[self showNoContactsModeIfNecessary];
[self showContactAppropriateViews];
}
- (BOOL)shouldHideLocalNumber

@ -168,7 +168,8 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS
@"SETTINGS_BUTTON_ACCESSIBILITY", @"Accessibility hint for the settings button");
self.missingContactsPermissionView.text = NSLocalizedString(@"INBOX_VIEW_MISSING_CONTACTS_PERMISSION", @"Multi line label explainging how to show names instead of phone numbers in your inbox");
self.missingContactsPermissionView.text = NSLocalizedString(@"INBOX_VIEW_MISSING_CONTACTS_PERMISSION",
@"Multiline label explaining how to show names instead of phone numbers in your inbox");
self.missingContactsPermissionView.tapAction = ^{
[[UIApplication sharedApplication] openSystemSettings];
};
@ -278,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 {

@ -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.

@ -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];

@ -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
@ -51,25 +59,36 @@ class SystemContactsFetcher: NSObject {
switch authorizationStatus {
case .notDetermined:
contactStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
contactStore.requestAccess(for: .contacts) { (granted, error) in
if let error = error {
Logger.error("\(self.TAG) error fetching contacts: \(error)")
DispatchQueue.main.async {
completion?(error)
}
return
}
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
}
if !granted {
// TODO, make this a one time dismissable admonishment
// e.g. remember across launches that the user has dismissed.
self.displayMissingContactsPermissionAlert()
} else {
self.updateContacts()
DispatchQueue.main.async {
self.updateContacts(completion: completion)
}
})
}
case .authorized:
// TODO reset onetime admonishment reminder, so that we remind user again (once) if they've since toggled permissions.
self.updateContacts()
self.updateContacts(completion: completion)
case .denied, .restricted:
Logger.debug("\(TAG) contacts were \(self.authorizationStatus)")
DispatchQueue.main.async {
completion?(nil)
}
}
}
@ -79,15 +98,10 @@ class SystemContactsFetcher: NSObject {
return
}
updateContacts()
}
private func displayMissingContactsPermissionAlert() {
let foo = UIApplication.shared.frontmostViewController
Logger.error("TODO")
updateContacts(completion: nil)
}
private func updateContacts() {
private func updateContacts(completion: ((Error?) -> Void)?) {
AssertIsOnMainThread()
systemContactsHaveBeenRequestedAtLeastOnce = true
@ -105,11 +119,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)
}
}
}
@ -123,7 +142,7 @@ class SystemContactsFetcher: NSObject {
@objc
private func contactStoreDidChange() {
updateContacts()
updateContacts(completion: nil)
}
}

@ -41,8 +41,9 @@ class ReminderView: UIView {
setupSubviews()
}
convenience init(tapAction: @escaping () -> Void) {
convenience init(text: String, tapAction: @escaping () -> Void) {
self.init(frame: .zero)
self.text = text
self.tapAction = tapAction
}

@ -1,9 +1,6 @@
/* Button text to dismiss missing contacts permission alert */
"AB_PERMISSION_MISSING_ACTION_NOT_NOW" = "Not Now";
/* Alert title when contacts disabled */
"AB_PERMISSION_MISSING_TITLE" = "Sorry!";
/* Action sheet item */
"ACCEPT_NEW_IDENTITY_ACTION" = "Accept new safety number";
@ -22,9 +19,6 @@
/* Title for the 'add group member' view. */
"ADD_GROUP_MEMBER_VIEW_TITLE" = "Add Member";
/* Alert body when contacts disabled */
"ADDRESSBOOK_RESTRICTED_ALERT_BODY" = "Signal requires access to your contacts. Access to contacts is restricted. Signal will close. You can disable the restriction temporarily to let Signal access your contacts by going the Settings app >> General >> Restrictions >> Contacts >> Allow Changes.";
/* The label for the 'discard' button in alerts and action sheets. */
"ALERT_DISCARD_BUTTON" = "Discard";
@ -223,6 +217,9 @@
/* Activity Sheet label */
"COMPARE_SAFETY_NUMBER_ACTION" = "Compare with Clipboard";
/* Multiline label explaining why compose-screen contact picker is empty. */
"COMPOSE_SCREEN_MISSING_CONTACTS_PERMISSION" = "To see which of your contacts are Signal users, enable contacts access in your system settings.";
/* No comment provided by engineer. */
"CONFIRM_ACCOUNT_DESTRUCTION_TEXT" = "This will reset the application by deleting your messages and unregister you with the server. The app will close after deletion of data.";
@ -568,7 +565,7 @@
/* Call setup status label */
"IN_CALL_TERMINATED" = "Call Ended.";
/* Multi line label explainging how to show names instead of phone numbers in your inbox */
/* Multiline label explaining how to show names instead of phone numbers in your inbox */
"INBOX_VIEW_MISSING_CONTACTS_PERMISSION" = "To see the names of your contacts, update your sytem settings to allow contact access.";
/* notification body */
@ -580,8 +577,13 @@
/* No comment provided by engineer. */
"INCOMING_INCOMPLETE_CALL" = "Incomplete incoming call from";
/* Label for the cell that presents the 'invite contacts' workflow.
Text for button at the top of the contact picker */
/* Alert body when contacts disabled while trying to invite contacts to signal */
"INVITE_FLOW_REQUIRES_CONTACT_ACCESS_BODY" = "To invite your contacts, you need to allow Signal access to your contacts in the Settings app.";
/* Alert title when contacts disabled while trying to invite contacts to signal */
"INVITE_FLOW_REQUIRES_CONTACT_ACCESS_TITLE" = "Enable Contact Access";
/* Label for the cell that presents the 'invite contacts' workflow. */
"INVITE_FRIENDS_CONTACT_TABLE_BUTTON" = "Invite Friends to Signal";
/* Search */

Loading…
Cancel
Save