diff --git a/Signal/src/ViewControllers/AddToBlockListViewController.m b/Signal/src/ViewControllers/AddToBlockListViewController.m index f027edcf7..ccc5fc369 100644 --- a/Signal/src/ViewControllers/AddToBlockListViewController.m +++ b/Signal/src/ViewControllers/AddToBlockListViewController.m @@ -3,7 +3,10 @@ // #import "AddToBlockListViewController.h" +#import "ContactTableViewCell.h" #import "CountryCodeViewController.h" +#import "Environment.h" +#import "OWSContactsManager.h" #import "PhoneNumber.h" #import "StringUtil.h" #import "UIFont+OWS.h" @@ -17,10 +20,14 @@ NS_ASSUME_NONNULL_BEGIN NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockListViewControllerCellIdentifier"; +NSString *const kContactsTable_CellReuseIdentifier = @"kContactsTable_CellReuseIdentifier"; + #pragma mark - -// TODO: Add a list of contacts to make it easier to block contacts. -@interface AddToBlockListViewController () +@interface AddToBlockListViewController () @property (nonatomic, readonly) OWSBlockingManager *blockingManager; @@ -31,20 +38,19 @@ NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockList @property (nonatomic) UIButton *blockButton; +@property (nonatomic) UITableView *contactsTableView; + @property (nonatomic) NSString *callingCode; +@property (nonatomic, readonly) OWSContactsManager *contactsManager; +@property (nonatomic) NSArray *contacts; + @end #pragma mark - @implementation AddToBlockListViewController -- (void)viewDidLoad -{ - [super viewDidLoad]; - [self.navigationController.navigationBar setTranslucent:NO]; -} - - (void)loadView { [super loadView]; @@ -52,6 +58,8 @@ NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockList self.view.backgroundColor = [UIColor whiteColor]; _blockingManager = [OWSBlockingManager sharedManager]; + _contactsManager = [Environment getCurrent].contactsManager; + self.contacts = self.contactsManager.signalContacts; self.title = NSLocalizedString(@"SETTINGS_ADD_TO_BLOCK_LIST_TITLE", @""); @@ -62,12 +70,22 @@ NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockList [self addNotificationListeners]; } +- (void)viewDidLoad +{ + [super viewDidLoad]; + [self.navigationController.navigationBar setTranslucent:NO]; +} + - (void)addNotificationListeners { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(blockedPhoneNumbersDidChange:) name:kNSNotificationName_BlockedPhoneNumbersDidChange object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(signalRecipientsDidChange:) + name:OWSContactsManagerSignalRecipientsDidChangeNotification + object:nil]; } - (void)dealloc @@ -164,6 +182,17 @@ NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockList [_blockButton autoSetDimension:ALDimensionWidth toSize:160]; [_blockButton autoSetDimension:ALDimensionHeight toSize:40]; + _contactsTableView = [UITableView new]; + _contactsTableView.dataSource = self; + _contactsTableView.delegate = self; + [_contactsTableView registerClass:[ContactTableViewCell class] + forCellReuseIdentifier:kContactsTable_CellReuseIdentifier]; + _contactsTableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; + [self.view addSubview:_contactsTableView]; + [_contactsTableView autoPinWidthToSuperview]; + [_contactsTableView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:blockButtonRow withOffset:30]; + [_contactsTableView autoPinToBottomLayoutGuideOfViewController:self withInset:0]; + [self updateBlockButtonEnabling]; } @@ -247,10 +276,11 @@ NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockList [_blockingManager addBlockedPhoneNumber:[parsedPhoneNumber toE164]]; UIAlertController *controller = [UIAlertController - alertControllerWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE", + alertControllerWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_PHONE_NUMBER_BLOCKED_ALERT_TITLE", @"The title of the 'phone number blocked' alert in the block view.") message:[NSString - stringWithFormat:NSLocalizedString(@"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT", + stringWithFormat:NSLocalizedString( + @"BLOCK_LIST_VIEW_PHONE_NUMBER_BLOCKED_ALERT_MESSAGE_FORMAT", @"The message format of the 'phone number blocked' alert in " @"the block view. Embeds {{the blocked phone number}}."), [parsedPhoneNumber toE164]] @@ -295,6 +325,19 @@ NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockList // TODO: Once we have a list of contacts, we should update it here. } +- (void)signalRecipientsDidChange:(NSNotification *)notification +{ + [self updateContacts]; +} + +- (void)updateContacts +{ + dispatch_async(dispatch_get_main_queue(), ^{ + self.contacts = self.contactsManager.signalContacts; + [self.contactsTableView reloadData]; + }); +} + #pragma mark - CountryCodeViewControllerDelegate - (void)countryCodeViewController:(CountryCodeViewController *)vc @@ -332,6 +375,113 @@ NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockList return NO; } +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return (NSInteger)self.contacts.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + Contact *contact = self.contacts[(NSUInteger)indexPath.item]; + + ContactTableViewCell *cell = [_contactsTableView cellForRowAtIndexPath:indexPath]; + if (!cell) { + cell = [ContactTableViewCell new]; + } + [cell configureWithContact:contact contactsManager:self.contactsManager]; + return cell; +} + +- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + return NSLocalizedString( + @"BLOCK_LIST_VIEW_CONTACTS_SECTION_TITLE", @"A title for the contacts section of the blocklist view."); +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return [ContactTableViewCell rowHeight]; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + Contact *contact = self.contacts[(NSUInteger)indexPath.item]; + [self showBlockActionSheet:contact]; +} + +- (void)showBlockActionSheet:(Contact *)contact +{ + OWSAssert(contact); + + NSString *displayName = contact.fullName; + + NSString *title = [NSString stringWithFormat:NSLocalizedString(@"BLOCK_LIST_BLOCK_TITLE_FORMAT", + @"A format for the 'block phone number' action sheet title."), + displayName]; + + UIAlertController *actionSheetController = + [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + __weak AddToBlockListViewController *weakSelf = self; + UIAlertAction *unblockAction = [UIAlertAction + actionWithTitle:NSLocalizedString(@"BLOCK_LIST_BLOCK_BUTTON", @"Button label for the 'block' button") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + [weakSelf blockContact:contact displayName:displayName]; + }]; + [actionSheetController addAction:unblockAction]; + + UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"") + style:UIAlertActionStyleCancel + handler:nil]; + [actionSheetController addAction:dismissAction]; + + [self presentViewController:actionSheetController animated:YES completion:nil]; +} + +- (void)blockContact:(Contact *)contact displayName:(NSString *)displayName +{ + for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) { + if (phoneNumber.toE164.length > 0) { + [_blockingManager addBlockedPhoneNumber:phoneNumber.toE164]; + } + } + + UIAlertController *controller = [UIAlertController + alertControllerWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_CONTACT_BLOCKED_ALERT_TITLE", + @"The title of the 'contact blocked' alert in the block view.") + message:[NSString stringWithFormat:NSLocalizedString( + @"BLOCK_LIST_VIEW_CONTACT_BLOCKED_ALERT_MESSAGE_FORMAT", + @"The message format of the 'contact blocked' " + @"alert in the block view. It is populated with the " + @"blocked contact's name."), + displayName] + preferredStyle:UIAlertControllerStyleAlert]; + + [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) + style:UIAlertActionStyleDefault + handler:nil]]; + [self presentViewController:controller animated:YES completion:nil]; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + [self.phoneNumberTextField resignFirstResponder]; +} + #pragma mark - Logging + (NSString *)tag diff --git a/Signal/src/ViewControllers/BlockListViewController.m b/Signal/src/ViewControllers/BlockListViewController.m index 8365a3e3c..701a85549 100644 --- a/Signal/src/ViewControllers/BlockListViewController.m +++ b/Signal/src/ViewControllers/BlockListViewController.m @@ -3,19 +3,24 @@ // #import "BlockListViewController.h" -#import "UIFont+OWS.h" -#import "PhoneNumber.h" #import "AddToBlockListViewController.h" +#import "Environment.h" +#import "OWSContactsManager.h" +#import "PhoneNumber.h" +#import "UIFont+OWS.h" #import NS_ASSUME_NONNULL_BEGIN -// TODO: We should label phone numbers with contact names where possible. @interface BlockListViewController () @property (nonatomic, readonly) OWSBlockingManager *blockingManager; @property (nonatomic, readonly) NSArray *blockedPhoneNumbers; +@property (nonatomic, readonly) OWSContactsManager *contactsManager; +@property (nonatomic) NSArray *contacts; +@property (nonatomic) NSDictionary *contactMap; + @end #pragma mark - @@ -39,6 +44,8 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { _blockingManager = [OWSBlockingManager sharedManager]; _blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers]; + _contactsManager = [Environment getCurrent].contactsManager; + self.contacts = self.contactsManager.signalContacts; self.title = NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE", @"Label for the block list section of the settings view"); @@ -52,6 +59,10 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { selector:@selector(blockedPhoneNumbersDidChange:) name:kNSNotificationName_BlockedPhoneNumbersDidChange object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(signalRecipientsDidChange:) + name:OWSContactsManagerSignalRecipientsDidChangeNotification + object:nil]; } - (void)dealloc @@ -105,20 +116,13 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { case BlockListViewControllerSection_Add: cell.textLabel.text = NSLocalizedString( @"SETTINGS_BLOCK_LIST_ADD_BUTTON", @"A label for the 'add phone number' button in the block list table."); - cell.textLabel.font = [UIFont ows_mediumFontWithSize:18.f]; + cell.textLabel.font = [UIFont ows_regularFontWithSize:18.f]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; break; case BlockListViewControllerSection_BlockList: { - NSString *phoneNumber = _blockedPhoneNumbers[(NSUInteger) indexPath.item]; - PhoneNumber *parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber]; - // Try to parse and present the phone number in E164. - // It should already be in E164, so this should always work. - // If an invalid or unparsable phone number is already in the block list, - // present it as-is. - cell.textLabel.text = (parsedPhoneNumber - ? parsedPhoneNumber.toE164 - : phoneNumber); - cell.textLabel.font = [UIFont ows_mediumFontWithSize:18.f]; + NSString *displayName = [self displayNameForIndexPath:indexPath]; + cell.textLabel.text = displayName; + cell.textLabel.font = [UIFont ows_regularFontWithSize:18.f]; cell.accessoryType = UITableViewCellAccessoryCheckmark; break; } @@ -130,6 +134,24 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { return cell; } +- (NSString *)displayNameForIndexPath:(NSIndexPath *)indexPath +{ + NSString *phoneNumber = _blockedPhoneNumbers[(NSUInteger)indexPath.item]; + PhoneNumber *parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber]; + + // Try to parse and present the phone number in E164. + // It should already be in E164, so this should always work. + // If an invalid or unparsable phone number is already in the block list, + // present it as-is. + NSString *displayName = (parsedPhoneNumber ? parsedPhoneNumber.toE164 : phoneNumber); + Contact *contact = self.contactMap[displayName]; + if (contact && [contact fullName].length > 0) { + displayName = [contact fullName]; + } + + return displayName; +} + - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; @@ -145,7 +167,8 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { } case BlockListViewControllerSection_BlockList: { NSString *phoneNumber = _blockedPhoneNumbers[(NSUInteger)indexPath.item]; - [self showUnblockActionSheet:phoneNumber]; + NSString *displayName = [self displayNameForIndexPath:indexPath]; + [self showUnblockActionSheet:phoneNumber displayName:displayName]; break; } default: @@ -153,16 +176,14 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { } } -- (void)showUnblockActionSheet:(NSString *)phoneNumber +- (void)showUnblockActionSheet:(NSString *)phoneNumber displayName:(NSString *)displayName { OWSAssert(phoneNumber.length > 0); - - PhoneNumber *parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber]; - NSString *displayPhoneNumber = (parsedPhoneNumber ? parsedPhoneNumber.toE164 : phoneNumber); + OWSAssert(displayName.length > 0); NSString *title = [NSString stringWithFormat:NSLocalizedString(@"BLOCK_LIST_UNBLOCK_TITLE_FORMAT", @"A format for the 'unblock phone number' action sheet title."), - displayPhoneNumber]; + displayName]; UIAlertController *actionSheetController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet]; @@ -172,7 +193,7 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { actionWithTitle:NSLocalizedString(@"BLOCK_LIST_UNBLOCK_BUTTON", @"Button label for the 'unblock' button") style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { - [weakSelf unblockPhoneNumber:phoneNumber displayPhoneNumber:displayPhoneNumber]; + [weakSelf unblockPhoneNumber:phoneNumber displayName:displayName]; }]; [actionSheetController addAction:unblockAction]; @@ -184,7 +205,7 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { [self presentViewController:actionSheetController animated:YES completion:nil]; } -- (void)unblockPhoneNumber:(NSString *)phoneNumber displayPhoneNumber:(NSString *)displayPhoneNumber +- (void)unblockPhoneNumber:(NSString *)phoneNumber displayName:(NSString *)displayName { [_blockingManager removeBlockedPhoneNumber:phoneNumber]; @@ -196,7 +217,7 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { @"The message format of the 'phone number unblocked' " @"alert in the block view. It is populated with the " @"blocked phone number."), - displayPhoneNumber] + displayName] preferredStyle:UIAlertControllerStyleAlert]; [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) @@ -214,6 +235,35 @@ typedef NS_ENUM(NSInteger, BlockListViewControllerSection) { [self.tableView reloadData]; } +- (void)signalRecipientsDidChange:(NSNotification *)notification +{ + [self updateContacts]; +} + +- (void)updateContacts +{ + dispatch_async(dispatch_get_main_queue(), ^{ + self.contacts = self.contactsManager.signalContacts; + [self.tableView reloadData]; + }); +} + +- (void)setContacts:(NSArray *)contacts +{ + _contacts = contacts; + + NSMutableDictionary *contactMap = [NSMutableDictionary new]; + for (Contact *contact in contacts) { + for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) { + NSString *phoneNumberE164 = phoneNumber.toE164; + if (phoneNumberE164.length > 0) { + contactMap[phoneNumberE164] = contact; + } + } + } + self.contactMap = contactMap; +} + #pragma mark - Logging + (NSString *)tag diff --git a/Signal/src/views/ContactTableViewCell.h b/Signal/src/views/ContactTableViewCell.h index 72f253dd0..91011d6c2 100644 --- a/Signal/src/views/ContactTableViewCell.h +++ b/Signal/src/views/ContactTableViewCell.h @@ -1,3 +1,7 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + #import #import "OWSContactsManager.h" @@ -13,6 +17,8 @@ NS_ASSUME_NONNULL_BEGIN @interface ContactTableViewCell : UITableViewCell ++ (CGFloat)rowHeight; + - (void)configureWithContact:(Contact *)contact contactsManager:(OWSContactsManager *)contactsManager; @end diff --git a/Signal/src/views/ContactTableViewCell.m b/Signal/src/views/ContactTableViewCell.m index f3a8a2b23..c45f4fd3c 100644 --- a/Signal/src/views/ContactTableViewCell.m +++ b/Signal/src/views/ContactTableViewCell.m @@ -6,7 +6,9 @@ #import "Environment.h" #import "OWSContactAvatarBuilder.h" #import "OWSContactsManager.h" +#import "UIFont+OWS.h" #import "UIUtil.h" +#import "UIView+OWS.h" NS_ASSUME_NONNULL_BEGIN @@ -19,11 +21,51 @@ NS_ASSUME_NONNULL_BEGIN @implementation ContactTableViewCell +- (instancetype)init +{ + if (self = [super init]) { + [self configureProgrammatically]; + } + return self; +} + - (nullable NSString *)reuseIdentifier { return NSStringFromClass(self.class); } ++ (CGFloat)rowHeight +{ + return 59.f; +} + +- (void)configureProgrammatically +{ + _avatarView = [UIImageView new]; + _avatarView.contentMode = UIViewContentModeScaleToFill; + _avatarView.image = [UIImage imageNamed:@"empty-group-avatar"]; + [self.contentView addSubview:_avatarView]; + + _nameLabel = [UILabel new]; + _nameLabel.contentMode = UIViewContentModeLeft; + _nameLabel.lineBreakMode = NSLineBreakByTruncatingTail; + _nameLabel.font = [UIFont ows_dynamicTypeBodyFont]; + [self.contentView addSubview:_nameLabel]; + + [_avatarView autoVCenterInSuperview]; + [_avatarView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:8.f]; + [_avatarView autoSetDimension:ALDimensionWidth toSize:40.f]; + [_avatarView autoSetDimension:ALDimensionHeight toSize:40.f]; + + [_nameLabel autoPinEdgeToSuperviewEdge:ALEdgeRight]; + [_nameLabel autoPinEdgeToSuperviewEdge:ALEdgeTop]; + [_nameLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom]; + [_nameLabel autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:_avatarView withOffset:12.f]; + + // Force layout, since imageView isn't being initally rendered on App Store optimized build. + [self layoutSubviews]; +} + - (void)configureWithContact:(Contact *)contact contactsManager:(OWSContactsManager *)contactsManager { self.nameLabel.attributedText = [contactsManager formattedFullNameForContact:contact font:self.nameLabel.font]; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index f69a496b3..b496d7a7f 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -97,6 +97,12 @@ /* No comment provided by engineer. */ "ATTACHMENT_QUEUED" = "New attachment queued for retrieval."; +/* Button label for the 'block' button */ +"BLOCK_LIST_BLOCK_BUTTON" = "Block"; + +/* A format for the 'block phone number' action sheet title. */ +"BLOCK_LIST_BLOCK_TITLE_FORMAT" = "Block %@?"; + /* Button label for the 'unblock' button */ "BLOCK_LIST_UNBLOCK_BUTTON" = "Unblock"; @@ -106,11 +112,20 @@ /* A label for the block button in the block list view */ "BLOCK_LIST_VIEW_BLOCK_BUTTON" = "Block"; +/* The message format of the 'contact blocked' alert in the block view. It is populated with the blocked contact's name. */ +"BLOCK_LIST_VIEW_CONTACT_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked"; + +/* The title of the 'contact blocked' alert in the block view. */ +"BLOCK_LIST_VIEW_CONTACT_BLOCKED_ALERT_TITLE" = "Contact Blocked"; + +/* A title for the contacts section of the blocklist view. */ +"BLOCK_LIST_VIEW_CONTACTS_SECTION_TITLE" = "Contacts"; + /* The message format of the 'phone number blocked' alert in the block view. It is populated with the blocked phone number. */ -"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked."; +"BLOCK_LIST_VIEW_PHONE_NUMBER_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked"; /* The title of the 'phone number blocked' alert in the block view. */ -"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Phone Number Blocked"; +"BLOCK_LIST_VIEW_PHONE_NUMBER_BLOCKED_ALERT_TITLE" = "Phone Number Blocked"; /* The message format of the 'phone number unblocked' alert in the block view. It is populated with the blocked phone number. */ "BLOCK_LIST_VIEW_UNBLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been unblocked.";