session-ios/Signal/src/ViewControllers/AddToBlockListViewController.m

600 lines
22 KiB
Mathematica
Raw Normal View History

//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "AddToBlockListViewController.h"
#import "BlockListUIUtils.h"
#import "ContactTableViewCell.h"
#import "CountryCodeViewController.h"
#import "Environment.h"
#import "OWSContactsManager.h"
#import "PhoneNumber.h"
#import "StringUtil.h"
#import "UIFont+OWS.h"
#import "UIUtil.h"
#import "UIView+OWS.h"
#import "ViewControllerUtils.h"
#import <SignalServiceKit/OWSBlockingManager.h>
#import <SignalServiceKit/PhoneNumberUtil.h>
#import <SignalServiceKit/TSAccountManager.h>
NS_ASSUME_NONNULL_BEGIN
NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockListViewControllerCellIdentifier";
#pragma mark -
@interface AddToBlockListViewController () <CountryCodeViewControllerDelegate,
UITextFieldDelegate,
UITableViewDataSource,
UITableViewDelegate>
@property (nonatomic, readonly) OWSBlockingManager *blockingManager;
@property (nonatomic, readonly) NSArray<NSString *> *blockedPhoneNumbers;
@property (nonatomic) UIButton *countryNameButton;
@property (nonatomic) UIButton *countryCodeButton;
@property (nonatomic) UITextField *phoneNumberTextField;
@property (nonatomic) UIButton *blockButton;
@property (nonatomic) UITableView *contactsTableView;
@property (nonatomic) NSString *callingCode;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic) NSArray<Contact *> *contacts;
@end
#pragma mark -
@implementation AddToBlockListViewController
- (void)loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
_blockingManager = [OWSBlockingManager sharedManager];
_blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers];
_contactsManager = [Environment getCurrent].contactsManager;
self.contacts = [self filteredContacts];
self.title = NSLocalizedString(@"SETTINGS_ADD_TO_BLOCK_LIST_TITLE", @"Title for the 'add to block list' view.");
[self createViews];
[self populateDefaultCountryNameAndCode];
[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
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)createViews {
// Block Phone Number Title Row
UIView *blockPhoneNumberTitleRow =
[self createTitleRowWithText:NSLocalizedString(@"SETTINGS_ADD_TO_BLOCK_LIST_BLOCK_PHONE_NUMBER_TITLE",
@"Title for the 'block phone number' section of the 'add to block list' view.")
previousRow:nil];
// Country Row
UIView *countryRow = [self createRowWithHeight:60 previousRow:blockPhoneNumberTitleRow];
_countryNameButton = [UIButton buttonWithType:UIButtonTypeCustom];
_countryNameButton.titleLabel.font = [UIFont ows_mediumFontWithSize:16.f];
[_countryNameButton setTitleColor:[UIColor blackColor]
forState:UIControlStateNormal];
2017-04-03 23:12:29 +02:00
[_countryNameButton
setTitle:NSLocalizedString(@"REGISTRATION_DEFAULT_COUNTRY_NAME", @"Label for the country code field")
forState:UIControlStateNormal];
[_countryNameButton addTarget:self
action:@selector(showCountryCodeView:)
forControlEvents:UIControlEventTouchUpInside];
[countryRow addSubview:_countryNameButton];
[_countryNameButton autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:20.f];
[_countryNameButton autoVCenterInSuperview];
_countryCodeButton = [UIButton buttonWithType:UIButtonTypeCustom];
_countryCodeButton.titleLabel.font = [UIFont ows_mediumFontWithSize:16.f];
_countryCodeButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
[_countryCodeButton setTitleColor:[UIColor ows_signalBrandBlueColor]
forState:UIControlStateNormal];
[_countryCodeButton addTarget:self
action:@selector(showCountryCodeView:)
forControlEvents:UIControlEventTouchUpInside];
[countryRow addSubview:_countryCodeButton];
[_countryCodeButton autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:16.f];
[_countryCodeButton autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:_countryNameButton withOffset:0];
[_countryCodeButton autoVCenterInSuperview];
// Border Row
UIView *borderRow1 = [self createRowWithHeight:1 previousRow:countryRow];
UIColor *borderColor = [UIColor colorWithRed:0.75f green:0.75f blue:0.75f alpha:1.f];
borderRow1.backgroundColor = borderColor;
// Phone Number Row
UIView *phoneNumberRow = [self createRowWithHeight:60 previousRow:borderRow1];
UILabel *phoneNumberLabel = [UILabel new];
phoneNumberLabel.font = [UIFont ows_mediumFontWithSize:16.f];
phoneNumberLabel.textColor = [UIColor blackColor];
2017-04-03 23:12:29 +02:00
phoneNumberLabel.text
= NSLocalizedString(@"REGISTRATION_PHONENUMBER_BUTTON", @"Label for the phone number textfield");
[phoneNumberRow addSubview:phoneNumberLabel];
[phoneNumberLabel autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:20.f];
[phoneNumberLabel autoVCenterInSuperview];
_phoneNumberTextField = [UITextField new];
_phoneNumberTextField.font = [UIFont ows_mediumFontWithSize:16.f];
_phoneNumberTextField.textAlignment = NSTextAlignmentRight;
_phoneNumberTextField.textColor = [UIColor ows_signalBrandBlueColor];
2017-04-03 23:12:29 +02:00
_phoneNumberTextField.placeholder = NSLocalizedString(
@"REGISTRATION_ENTERNUMBER_DEFAULT_TEXT", @"Placeholder text for the phone number textfield");
_phoneNumberTextField.keyboardType = UIKeyboardTypeNumberPad;
_phoneNumberTextField.delegate = self;
[_phoneNumberTextField addTarget:self
action:@selector(textFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
[phoneNumberRow addSubview:_phoneNumberTextField];
[_phoneNumberTextField autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:16.f];
[_phoneNumberTextField autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:phoneNumberLabel withOffset:0];
[_phoneNumberTextField autoVCenterInSuperview];
// Border Row
UIView *borderRow2 = [self createRowWithHeight:1 previousRow:phoneNumberRow];
borderRow2.backgroundColor = borderColor;
// Block Button Row
UIView *blockButtonRow = [self createRowWithHeight:60 previousRow:borderRow2];
2017-03-31 19:57:12 +02:00
// TODO: Eventually we should make a view factory that will allow us to
// create views with consistent appearance across the app and move
// towards a "design language."
_blockButton = [UIButton buttonWithType:UIButtonTypeCustom];
_blockButton.titleLabel.font = [UIFont ows_mediumFontWithSize:16.f];
[_blockButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_blockButton setBackgroundColor:[UIColor ows_signalBrandBlueColor]];
_blockButton.clipsToBounds = YES;
_blockButton.layer.cornerRadius = 3.f;
[_blockButton setTitle:NSLocalizedString(
@"BLOCK_LIST_VIEW_BLOCK_BUTTON", @"A label for the block button in the block list view")
forState:UIControlStateNormal];
[_blockButton addTarget:self action:@selector(blockButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[blockButtonRow addSubview:_blockButton];
[_blockButton autoCenterInSuperview];
[_blockButton autoSetDimension:ALDimensionWidth toSize:160];
[_blockButton autoSetDimension:ALDimensionHeight toSize:40];
// Separator Row
UIView *separatorRow = [self createRowWithHeight:10 previousRow:blockButtonRow];
// Block Contact Title Row
UIView *blockContactTitleRow =
[self createTitleRowWithText:NSLocalizedString(@"SETTINGS_ADD_TO_BLOCK_LIST_BLOCK_CONTACT_TITLE",
@"Title for the 'block contact' section of the 'add to block list' view.")
previousRow:separatorRow];
_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:blockContactTitleRow withOffset:10];
[_contactsTableView autoPinToBottomLayoutGuideOfViewController:self withInset:0];
[self updateBlockButtonEnabling];
}
- (UIView *)createTitleRowWithText:(NSString *)text previousRow:(nullable UIView *)previousRow
{
UIView *row = [self createRowWithHeight:40 previousRow:previousRow];
UILabel *label = [UILabel new];
label.text = text;
label.font = [UIFont ows_mediumFontWithSize:20.f];
label.textColor = [UIColor colorWithWhite:0.3f alpha:1.f];
label.textAlignment = NSTextAlignmentCenter;
[row addSubview:label];
[label autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:20.f];
[label autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:20.f];
[label autoPinEdgeToSuperviewEdge:ALEdgeBottom];
return row;
}
- (UIView *)createRowWithHeight:(CGFloat)height previousRow:(nullable UIView *)previousRow
{
UIView *row = [UIView new];
[self.view addSubview:row];
[row autoPinWidthToSuperview];
if (previousRow) {
[row autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:previousRow withOffset:0];
} else {
[row autoPinToTopLayoutGuideOfViewController:self withInset:0];
}
[row autoSetDimension:ALDimensionHeight toSize:height];
return row;
}
#pragma mark - Country
- (void)populateDefaultCountryNameAndCode {
NSLocale *locale = NSLocale.currentLocale;
NSString *countryCode = [locale objectForKey:NSLocaleCountryCode];
NSNumber *callingCode = [[PhoneNumberUtil sharedUtil].nbPhoneNumberUtil getCountryCodeForRegion:countryCode];
NSString *countryName = [PhoneNumberUtil countryNameFromCountryCode:countryCode];
[self updateCountryWithName:countryName
callingCode:[NSString stringWithFormat:@"%@%@",
COUNTRY_CODE_PREFIX,
callingCode]
countryCode:countryCode];
}
- (void)updateCountryWithName:(NSString *)countryName
callingCode:(NSString *)callingCode
countryCode:(NSString *)countryCode {
_callingCode = callingCode;
NSString *title = [NSString stringWithFormat:@"%@ (%@)",
callingCode,
countryCode.uppercaseString];
[_countryCodeButton setTitle:title
forState:UIControlStateNormal];
[_countryCodeButton layoutSubviews];
}
2017-04-03 23:12:29 +02:00
- (void)setCallingCode:(NSString *)callingCode
{
_callingCode = callingCode;
[self updateBlockButtonEnabling];
}
#pragma mark - Actions
- (void)showCountryCodeView:(id)sender {
CountryCodeViewController *countryCodeController = [[UIStoryboard storyboardWithName:@"Registration" bundle:NULL]
instantiateViewControllerWithIdentifier:@"CountryCodeViewController"];
countryCodeController.delegate = self;
countryCodeController.shouldDismissWithoutSegue = YES;
UINavigationController *navigationController =
[[UINavigationController alloc] initWithRootViewController:countryCodeController];
[self presentViewController:navigationController animated:YES completion:[UIUtil modalCompletionBlock]];
}
- (void)blockButtonPressed:(id)sender
{
[self tryToBlockPhoneNumber];
}
- (void)tryToBlockPhoneNumber
{
if (![self hasValidPhoneNumber]) {
2017-04-03 23:12:29 +02:00
OWSAssert(0);
return;
}
NSString *possiblePhoneNumber = [self.callingCode stringByAppendingString:_phoneNumberTextField.text.digitsOnly];
PhoneNumber *parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:possiblePhoneNumber];
OWSAssert(parsedPhoneNumber);
__weak AddToBlockListViewController *weakSelf = self;
[BlockListUIUtils showBlockPhoneNumberActionSheet:[parsedPhoneNumber toE164]
fromViewController:self
blockingManager:_blockingManager
2017-04-04 16:19:47 +02:00
contactsManager:_contactsManager
completionBlock:^(BOOL isBlocked) {
if (isBlocked) {
2017-04-05 15:09:09 +02:00
// Clear phone number text field if block succeeds.
weakSelf.phoneNumberTextField.text = nil;
2017-04-04 16:19:47 +02:00
[weakSelf.navigationController popViewControllerAnimated:YES];
}
}];
}
- (void)textFieldDidChange:(id)sender
{
[self updateBlockButtonEnabling];
}
// TODO: We could also do this in registration view.
- (BOOL)hasValidPhoneNumber
{
if (!self.callingCode) {
return NO;
}
NSString *possiblePhoneNumber = [self.callingCode stringByAppendingString:_phoneNumberTextField.text.digitsOnly];
PhoneNumber *parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:possiblePhoneNumber];
// It'd be nice to use [PhoneNumber isValid] but it always returns false for some countries
// (like afghanistan) and there doesn't seem to be a good way to determine beforehand
// which countries it can validate for without forking libPhoneNumber.
return parsedPhoneNumber && parsedPhoneNumber.toE164.length > 1;
}
- (void)updateBlockButtonEnabling
{
BOOL isEnabled = [self hasValidPhoneNumber];
_blockButton.enabled = isEnabled;
[_blockButton setBackgroundColor:(isEnabled ? [UIColor ows_signalBrandBlueColor] : [UIColor lightGrayColor])];
}
- (void)blockedPhoneNumbersDidChange:(id)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
_blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers];
[self updateContacts];
});
}
- (void)signalRecipientsDidChange:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
[self updateContacts];
});
}
- (void)updateContacts
{
OWSAssert([NSThread isMainThread]);
self.contacts = [self filteredContacts];
[self.contactsTableView reloadData];
}
- (BOOL)isContactBlocked:(Contact *)contact
{
if (contact.parsedPhoneNumbers.count < 1) {
2017-04-05 18:22:35 +02:00
// Hide contacts without any valid phone numbers.
return NO;
}
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
if ([_blockedPhoneNumbers containsObject:phoneNumber.toE164]) {
return YES;
}
}
return NO;
}
- (BOOL)isContactHidden:(Contact *)contact
{
if (contact.parsedPhoneNumbers.count < 1) {
// Hide contacts without any valid phone numbers.
return YES;
}
if ([self isCurrentUserContact:contact]) {
return YES;
}
return NO;
}
- (BOOL)isCurrentUserContact:(Contact *)contact
{
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
if ([[phoneNumber toE164] isEqualToString:[TSAccountManager localNumber]]) {
return YES;
}
}
return NO;
}
- (NSArray<Contact *> *_Nonnull)filteredContacts
{
NSMutableArray<Contact *> *result = [NSMutableArray new];
for (Contact *contact in self.contactsManager.signalContacts) {
if (![self isContactHidden:contact]) {
[result addObject:contact];
}
}
2017-04-05 18:22:35 +02:00
return [result copy];
}
#pragma mark - CountryCodeViewControllerDelegate
- (void)countryCodeViewController:(CountryCodeViewController *)vc
didSelectCountryCode:(NSString *)countryCode
countryName:(NSString *)countryName
callingCode:(NSString *)callingCode {
[self updateCountryWithName:countryName
callingCode:callingCode
countryCode:countryCode];
[self textField:_phoneNumberTextField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
}
#pragma mark - UITextFieldDelegate
// TODO: This logic resides in both RegistrationViewController and here.
// We should refactor it out into a utility function.
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
{
[ViewControllerUtils phoneNumberTextField:textField
shouldChangeCharactersInRange:range
replacementString:insertionText
countryCode:_callingCode];
[self updateBlockButtonEnabling];
return NO; // inform our caller that we took care of performing the change
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
[self tryToBlockPhoneNumber];
return NO;
}
#pragma mark - UITableViewDataSource
- (BOOL)hasNoContacts
{
return self.contacts.count == 0;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (self.hasNoContacts) {
return 1;
}
return (NSInteger)self.contacts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.hasNoContacts) {
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;
return cell;
}
Contact *contact = self.contacts[(NSUInteger)indexPath.item];
ContactTableViewCell *cell = [_contactsTableView cellForRowAtIndexPath:indexPath];
if (!cell) {
cell = [ContactTableViewCell new];
}
BOOL isBlocked = [self isContactBlocked:contact];
if (isBlocked) {
cell.accessoryMessage
= NSLocalizedString(@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
} else {
OWSAssert(cell.accessoryMessage == nil);
}
[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 block list view.");
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [ContactTableViewCell rowHeight];
}
#pragma mark - UITableViewDelegate
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.hasNoContacts) {
// Don't let user select the "you have no contacts" cell.
return nil;
}
return indexPath;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (self.hasNoContacts) {
return;
}
2017-04-04 16:19:47 +02:00
__weak AddToBlockListViewController *weakSelf = self;
Contact *contact = self.contacts[(NSUInteger)indexPath.item];
if ([self isContactBlocked:contact]) {
NSString *displayName = [_contactsManager displayNameForContact:contact];
UIAlertController *controller = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_ALREADY_BLOCKED_ALERT_TITLE",
@"A title of the alert if user tries to block a user who is already blocked.")
message:[NSString
stringWithFormat:NSLocalizedString(
@"BLOCK_LIST_VIEW_ALREADY_BLOCKED_ALERT_MESSAGE_FORMAT",
@"A format for the message of the alert if user tries to "
@"block a user who is already blocked. Embeds {{the "
@"blocked user's name or phone number}}."),
displayName]
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:nil]];
[self presentViewController:controller animated:YES completion:nil];
return;
}
[BlockListUIUtils showBlockContactActionSheet:contact
fromViewController:self
blockingManager:_blockingManager
2017-04-04 16:19:47 +02:00
contactsManager:_contactsManager
completionBlock:^(BOOL isBlocked) {
if (isBlocked) {
[weakSelf.navigationController popViewControllerAnimated:YES];
}
}];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self.phoneNumberTextField resignFirstResponder];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END