Merge branch 'charlesmchen/blocking2'

This commit is contained in:
Matthew Chen 2017-04-03 17:50:42 -04:00
commit 06ad8733b2
18 changed files with 1120 additions and 302 deletions

View file

@ -55,6 +55,10 @@
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */; };
34B3F8941E8DF1710035BE1A /* SignalsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8701E8DF1700035BE1A /* SignalsViewController.m */; };
34B3F8991E8DF1B90035BE1A /* TSMessageAdapterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8981E8DF1B90035BE1A /* TSMessageAdapterTest.m */; };
34B3F89C1E8DF3270035BE1A /* BlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F89B1E8DF3270035BE1A /* BlockListViewController.m */; };
34B3F89F1E8DF5490035BE1A /* OWSTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F89E1E8DF5490035BE1A /* OWSTableViewController.m */; };
34B3F8A21E8EA6040035BE1A /* ViewControllerUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */; };
34DFCB851E8E04B500053165 /* AddToBlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */; };
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */; };
450573FE1E78A06D00615BB4 /* OWS103EnableVideoCalling.m in Sources */ = {isa = PBXBuildFile; fileRef = 450573FD1E78A06D00615BB4 /* OWS103EnableVideoCalling.m */; };
4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4505C2BE1E648EA300CEBF41 /* ExperienceUpgrade.swift */; };
@ -415,6 +419,14 @@
34B3F86F1E8DF1700035BE1A /* SignalsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalsViewController.h; sourceTree = "<group>"; };
34B3F8701E8DF1700035BE1A /* SignalsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalsViewController.m; sourceTree = "<group>"; };
34B3F8981E8DF1B90035BE1A /* TSMessageAdapterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSMessageAdapterTest.m; sourceTree = "<group>"; };
34B3F89A1E8DF3270035BE1A /* BlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockListViewController.h; sourceTree = "<group>"; };
34B3F89B1E8DF3270035BE1A /* BlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlockListViewController.m; sourceTree = "<group>"; };
34B3F89D1E8DF5490035BE1A /* OWSTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSTableViewController.h; sourceTree = "<group>"; };
34B3F89E1E8DF5490035BE1A /* OWSTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSTableViewController.m; sourceTree = "<group>"; };
34B3F8A01E8EA6040035BE1A /* ViewControllerUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewControllerUtils.h; sourceTree = "<group>"; };
34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewControllerUtils.m; sourceTree = "<group>"; };
34DFCB831E8E04B400053165 /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = "<group>"; };
34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToBlockListViewController.m; sourceTree = "<group>"; };
34FD936E1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSAnyTouchGestureRecognizer.h; path = views/OWSAnyTouchGestureRecognizer.h; sourceTree = "<group>"; };
34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSAnyTouchGestureRecognizer.m; path = views/OWSAnyTouchGestureRecognizer.m; sourceTree = "<group>"; };
450573FC1E78A06D00615BB4 /* OWS103EnableVideoCalling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS103EnableVideoCalling.h; path = Migrations/OWS103EnableVideoCalling.h; sourceTree = "<group>"; };
@ -804,11 +816,15 @@
children = (
34B3F8341E8DF1700035BE1A /* AboutTableViewController.h */,
34B3F8351E8DF1700035BE1A /* AboutTableViewController.m */,
34DFCB831E8E04B400053165 /* AddToBlockListViewController.h */,
34DFCB841E8E04B500053165 /* AddToBlockListViewController.m */,
34B3F8361E8DF1700035BE1A /* AdvancedSettingsTableViewController.h */,
34B3F8371E8DF1700035BE1A /* AdvancedSettingsTableViewController.m */,
34B3F8381E8DF1700035BE1A /* AttachmentApprovalViewController.swift */,
34B3F8391E8DF1700035BE1A /* AttachmentSharing.h */,
34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */,
34B3F89A1E8DF3270035BE1A /* BlockListViewController.h */,
34B3F89B1E8DF3270035BE1A /* BlockListViewController.m */,
34B3F83B1E8DF1700035BE1A /* CallViewController.swift */,
34B3F83C1E8DF1700035BE1A /* CodeVerificationViewController.h */,
34B3F83D1E8DF1700035BE1A /* CodeVerificationViewController.m */,
@ -850,6 +866,8 @@
34B3F8611E8DF1700035BE1A /* OWSMessagesToolbarContentView.xib */,
34B3F8621E8DF1700035BE1A /* OWSQRCodeScanningViewController.h */,
34B3F8631E8DF1700035BE1A /* OWSQRCodeScanningViewController.m */,
34B3F89D1E8DF5490035BE1A /* OWSTableViewController.h */,
34B3F89E1E8DF5490035BE1A /* OWSTableViewController.m */,
34B3F8641E8DF1700035BE1A /* PrivacySettingsTableViewController.h */,
34B3F8651E8DF1700035BE1A /* PrivacySettingsTableViewController.m */,
34B3F8661E8DF1700035BE1A /* RegistrationViewController.h */,
@ -863,6 +881,8 @@
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */,
34B3F86F1E8DF1700035BE1A /* SignalsViewController.h */,
34B3F8701E8DF1700035BE1A /* SignalsViewController.m */,
34B3F8A01E8EA6040035BE1A /* ViewControllerUtils.h */,
34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */,
);
path = ViewControllers;
sourceTree = "<group>";
@ -1966,6 +1986,7 @@
45C681C61D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m in Sources */,
34B3F8861E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m in Sources */,
452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */,
34DFCB851E8E04B500053165 /* AddToBlockListViewController.m in Sources */,
34B3F8321E8DF11D0035BE1A /* GroupContactsResult.m in Sources */,
45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */,
45666EC61D99483D008FE134 /* OWSAvatarBuilder.m in Sources */,
@ -1974,6 +1995,7 @@
76EB068618170B34006006FC /* ContactTableViewCell.m in Sources */,
34B3F8881E8DF1700035BE1A /* OversizeTextMessageViewController.swift in Sources */,
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
34B3F8A21E8EA6040035BE1A /* ViewControllerUtils.m in Sources */,
453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */,
45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */,
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
@ -2010,9 +2032,11 @@
458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */,
451DE9FD1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */,
45666F761D9BFE00008FE134 /* OWS100RemoveTSRecipientsMigration.m in Sources */,
34B3F89F1E8DF5490035BE1A /* OWSTableViewController.m in Sources */,
FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */,
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */,
34B3F89C1E8DF3270035BE1A /* BlockListViewController.m in Sources */,
45F2B1941D9C9F48000D2C69 /* OWSOutgoingMessageCollectionViewCell.m in Sources */,
BFB074C919A5611000F2947C /* ObservableValue.m in Sources */,
B68EF9BA1C0B1EBD009C3DCD /* FLAnimatedImage.m in Sources */,

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="tuk-0x-yCb">
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1217" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="tuk-0x-yCb">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
@ -678,7 +678,7 @@
<objects>
<navigationController storyboardIdentifier="UserInitialViewController" automaticallyAdjustsScrollViewInsets="NO" useStoryboardIdentifierAsRestorationIdentifier="YES" id="tuk-0x-yCb" customClass="SignalsNavigationController" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" id="VNq-cN-pk9">
<navigationBar key="navigationBar" contentMode="scaleToFill" id="VNq-cN-pk9">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="barTintColor" red="0.082137122750282288" green="0.46843802928924561" blue="0.91112053394317627" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -1163,7 +1163,7 @@
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="cZ7-de-SUi" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" translucent="NO" id="gzw-fh-en2">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="gzw-fh-en2">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="barTintColor" red="0.082137122750282288" green="0.46843802928924561" blue="0.91112053394317627" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -1288,11 +1288,11 @@
<viewControllerLayoutGuide type="bottom" id="kH6-9L-pzh"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="P0X-AM-Yjw">
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ukg-om-VX3" userLabel="Group Details">
<rect key="frame" x="0.0" y="0.0" width="375" height="100"/>
<rect key="frame" x="0.0" y="20" width="375" height="100"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ul8-NY-i4c">
<rect key="frame" x="8" y="20" width="60" height="60"/>
@ -1327,7 +1327,7 @@
</constraints>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsMultipleSelection="YES" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="cFo-AT-Srf">
<rect key="frame" x="0.0" y="108" width="375" height="495"/>
<rect key="frame" x="0.0" y="128" width="375" height="539"/>
<color key="backgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.94901960780000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<view key="tableHeaderView" contentMode="scaleToFill" id="ekO-kw-iHV" userLabel="Header View">
<rect key="frame" x="0.0" y="0.0" width="375" height="40"/>

View file

@ -0,0 +1,9 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AddToBlockListViewController : UIViewController
@end

View file

@ -0,0 +1,349 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "AddToBlockListViewController.h"
#import "CountryCodeViewController.h"
#import "PhoneNumber.h"
#import "StringUtil.h"
#import "UIFont+OWS.h"
#import "UIUtil.h"
#import "UIView+OWS.h"
#import "ViewControllerUtils.h"
#import <SignalServiceKit/PhoneNumberUtil.h>
#import <SignalServiceKit/OWSBlockingManager.h>
NS_ASSUME_NONNULL_BEGIN
NSString * const kAddToBlockListViewControllerCellIdentifier = @"kAddToBlockListViewControllerCellIdentifier";
#pragma mark -
// TODO: Add a list of contacts to make it easier to block contacts.
@interface AddToBlockListViewController () <CountryCodeViewControllerDelegate, UITextFieldDelegate>
@property (nonatomic, readonly) OWSBlockingManager *blockingManager;
@property (nonatomic) UIButton *countryNameButton;
@property (nonatomic) UIButton *countryCodeButton;
@property (nonatomic) UITextField *phoneNumberTextField;
@property (nonatomic) UIButton *blockButton;
@property (nonatomic) NSString *callingCode;
@end
#pragma mark -
@implementation AddToBlockListViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController.navigationBar setTranslucent:NO];
}
- (void)loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
_blockingManager = [OWSBlockingManager sharedManager];
self.title = NSLocalizedString(@"SETTINGS_ADD_TO_BLOCK_LIST_TITLE", @"");
[self createViews];
[self populateDefaultCountryNameAndCode];
[self addNotificationListeners];
}
- (void)addNotificationListeners
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(blockedPhoneNumbersDidChange:)
name:kNSNotificationName_BlockedPhoneNumbersDidChange
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)createViews {
// Country Row
UIView *countryRow = [self createRowWithHeight:60 previousRow:nil];
_countryNameButton = [UIButton buttonWithType:UIButtonTypeCustom];
_countryNameButton.titleLabel.font = [UIFont ows_mediumFontWithSize:16.f];
[_countryNameButton setTitleColor:[UIColor blackColor]
forState:UIControlStateNormal];
[_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];
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];
_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];
// 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];
[self updateBlockButtonEnabling];
}
- (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];
}
- (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]) {
OWSAssert(0);
return;
}
NSString *possiblePhoneNumber = [self.callingCode stringByAppendingString:_phoneNumberTextField.text.digitsOnly];
PhoneNumber *parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:possiblePhoneNumber];
OWSAssert(parsedPhoneNumber);
[_blockingManager addBlockedPhoneNumber:[parsedPhoneNumber toE164]];
UIAlertController *controller = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_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",
@"The message format of the 'phone number blocked' alert in "
@"the block view. Embeds {{the blocked phone number}}."),
[parsedPhoneNumber toE164]]
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:nil]];
[self presentViewController:controller animated:YES completion:nil];
_phoneNumberTextField.text = nil;
}
- (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
{
// TODO: Once we have a list of contacts, we should update it here.
}
#pragma mark - CountryCodeViewControllerDelegate
- (void)countryCodeViewController:(CountryCodeViewController *)vc
didSelectCountryCode:(NSString *)countryCode
countryName:(NSString *)countryName
callingCode:(NSString *)callingCode {
[self updateCountryWithName:countryName
callingCode:callingCode
countryCode:countryCode];
}
#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 - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,9 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface BlockListViewController : UITableViewController
@end

View file

@ -0,0 +1,231 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "BlockListViewController.h"
#import "UIFont+OWS.h"
#import "PhoneNumber.h"
#import "AddToBlockListViewController.h"
#import <SignalServiceKit/OWSBlockingManager.h>
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<NSString *> *blockedPhoneNumbers;
@end
#pragma mark -
typedef NS_ENUM(NSInteger, BlockListViewControllerSection) {
BlockListViewControllerSection_Add,
BlockListViewControllerSection_BlockList,
BlockListViewControllerSection_Count // meta section
};
@implementation BlockListViewController
- (instancetype)init
{
return [super initWithStyle:UITableViewStyleGrouped];
}
- (void)loadView
{
[super loadView];
_blockingManager = [OWSBlockingManager sharedManager];
_blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers];
self.title
= NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE", @"Label for the block list section of the settings view");
[self addNotificationListeners];
}
- (void)addNotificationListeners
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(blockedPhoneNumbersDidChange:)
name:kNSNotificationName_BlockedPhoneNumbersDidChange
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController.navigationBar setTranslucent:NO];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return BlockListViewControllerSection_Count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
switch (section) {
case BlockListViewControllerSection_Add:
return 1;
case BlockListViewControllerSection_BlockList:
return (NSInteger) _blockedPhoneNumbers.count;
default:
OWSAssert(0);
return 0;
}
}
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
switch (section) {
case BlockListViewControllerSection_Add:
return NSLocalizedString(
@"SETTINGS_BLOCK_LIST_HEADER_TITLE", @"A header title for the block list table.");
default:
return nil;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [UITableViewCell new];
OWSAssert(cell);
switch (indexPath.section) {
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.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];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
break;
}
default:
OWSAssert(0);
return 0;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
switch (indexPath.section) {
case BlockListViewControllerSection_Add:
{
AddToBlockListViewController *vc = [[AddToBlockListViewController alloc] init];
NSAssert(self.navigationController != nil, @"Navigation controller must not be nil");
NSAssert(vc != nil, @"Privacy Settings View Controller must not be nil");
[self.navigationController pushViewController:vc animated:YES];
break;
}
case BlockListViewControllerSection_BlockList: {
NSString *phoneNumber = _blockedPhoneNumbers[(NSUInteger)indexPath.item];
[self showUnblockActionSheet:phoneNumber];
break;
}
default:
OWSAssert(0);
}
}
- (void)showUnblockActionSheet:(NSString *)phoneNumber
{
OWSAssert(phoneNumber.length > 0);
PhoneNumber *parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber];
NSString *displayPhoneNumber = (parsedPhoneNumber ? parsedPhoneNumber.toE164 : phoneNumber);
NSString *title = [NSString stringWithFormat:NSLocalizedString(@"BLOCK_LIST_UNBLOCK_TITLE_FORMAT",
@"A format for the 'unblock phone number' action sheet title."),
displayPhoneNumber];
UIAlertController *actionSheetController =
[UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet];
__weak BlockListViewController *weakSelf = self;
UIAlertAction *unblockAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"BLOCK_LIST_UNBLOCK_BUTTON", @"Button label for the 'unblock' button")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[weakSelf unblockPhoneNumber:phoneNumber displayPhoneNumber:displayPhoneNumber];
}];
[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)unblockPhoneNumber:(NSString *)phoneNumber displayPhoneNumber:(NSString *)displayPhoneNumber
{
[_blockingManager removeBlockedPhoneNumber:phoneNumber];
UIAlertController *controller = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE",
@"The title of the 'phone number unblocked' alert in the block view.")
message:[NSString stringWithFormat:NSLocalizedString(
@"BLOCK_LIST_VIEW_UNBLOCKED_ALERT_MESSAGE_FORMAT",
@"The message format of the 'phone number unblocked' "
@"alert in the block view. It is populated with the "
@"blocked phone number."),
displayPhoneNumber]
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:nil]];
[self presentViewController:controller animated:YES completion:nil];
}
#pragma mark - Actions
- (void)blockedPhoneNumbersDidChange:(id)notification
{
_blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers];
[self.tableView reloadData];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

View file

@ -9,21 +9,21 @@
@protocol CountryCodeViewControllerDelegate <NSObject>
- (void)countryCodeViewController:(CountryCodeViewController *)vc
didSelectCountryCode:(NSString *)code
forCountry:(NSString *)country;
- (void)countryCodeViewControllerDidCancel:(CountryCodeViewController *)vc;
didSelectCountryCode:(NSString *)countryCode
countryName:(NSString *)countryName
callingCode:(NSString *)callingCode;
@end
@interface CountryCodeViewController
: UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate>
@property (nonatomic, strong) IBOutlet UITableView *countryCodeTableView;
@property (nonatomic, strong) IBOutlet UISearchBar *searchBar;
@property (nonatomic, assign) id<CountryCodeViewControllerDelegate> delegate;
@property (nonatomic, strong) NSString *countryCodeSelected;
@property (nonatomic, strong) NSString *callingCodeSelected;
@property (nonatomic, strong) NSString *countryNameSelected;
@property (nonatomic) IBOutlet UITableView *countryCodeTableView;
@property (nonatomic) IBOutlet UISearchBar *searchBar;
@property (nonatomic, weak) id<CountryCodeViewControllerDelegate> delegate;
@property (nonatomic) NSString *countryCodeSelected;
@property (nonatomic) NSString *callingCodeSelected;
@property (nonatomic) NSString *countryNameSelected;
@property (nonatomic) BOOL shouldDismissWithoutSegue;
@end

View file

@ -10,7 +10,6 @@
static NSString *const CONTRY_CODE_TABLE_CELL_IDENTIFIER = @"CountryCodeTableViewCell";
static NSString *const kUnwindToCountryCodeWasSelectedSegue = @"UnwindToCountryCodeWasSelectedSegue";
@interface CountryCodeViewController () {
NSArray *_countryCodes;
}
@ -25,6 +24,11 @@ static NSString *const kUnwindToCountryCodeWasSelectedSegue = @"UnwindToCountryC
_countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:nil];
self.title = NSLocalizedString(@"COUNTRYCODE_SELECT_TITLE", @"");
self.searchBar.delegate = self;
if (self.shouldDismissWithoutSegue) {
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(dismissWasPressed:)];
}
}
#pragma mark - UITableViewDelegate
@ -57,8 +61,20 @@ static NSString *const kUnwindToCountryCodeWasSelectedSegue = @"UnwindToCountryC
_callingCodeSelected = [PhoneNumberUtil callingCodeFromCountryCode:countryCode];
_countryNameSelected = [PhoneNumberUtil countryNameFromCountryCode:countryCode];
_countryCodeSelected = countryCode;
[self.delegate countryCodeViewController:self
didSelectCountryCode:_countryCodeSelected
countryName:_countryNameSelected
callingCode:_callingCodeSelected];
[self.searchBar resignFirstResponder];
[self performSegueWithIdentifier:kUnwindToCountryCodeWasSelectedSegue sender:self];
if (self.shouldDismissWithoutSegue) {
[self dismissViewControllerAnimated:YES completion:nil];
} else {
[self performSegueWithIdentifier:kUnwindToCountryCodeWasSelectedSegue sender:self];
}
}
- (void)dismissWasPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

View file

@ -2,13 +2,17 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "OWSTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface DebugUITableViewController : UITableViewController
@interface DebugUITableViewController : OWSTableViewController
+ (void)presentDebugUIForThread:(TSThread *)thread
fromViewController:(UIViewController *)fromViewController;
@end
NS_ASSUME_NONNULL_END

View file

@ -11,205 +11,8 @@
NS_ASSUME_NONNULL_BEGIN
@class OWSTableItem;
@class OWSTableSection;
@interface OWSTableContents : NSObject
@property (nonatomic) NSString *title;
@property (nonatomic) NSMutableArray<OWSTableSection *> *sections;
@end
#pragma mark -
@implementation OWSTableContents
-(instancetype)init {
if (self = [super init]) {
_sections = [NSMutableArray new];
}
return self;
}
- (void)addSection:(OWSTableSection *)section {
OWSAssert(section);
[_sections addObject:section];
}
@end
#pragma mark -
@interface OWSTableSection : NSObject
@property (nonatomic) NSString *title;
@property (nonatomic) NSMutableArray<OWSTableItem *> *items;
@end
#pragma mark -
@implementation OWSTableSection
+ (OWSTableSection *)sectionWithTitle:(NSString *)title
items:(NSArray *)items {
OWSTableSection *section = [OWSTableSection new];
section.title = title;
section.items = [items mutableCopy];
return section;
}
-(instancetype)init {
if (self = [super init]) {
_items = [NSMutableArray new];
}
return self;
}
- (void)addItem:(OWSTableItem *)item {
OWSAssert(item);
if (!_items) {
_items = [NSMutableArray new];
}
[_items addObject:item];
}
@end
#pragma mark -
typedef NS_ENUM(NSInteger, OWSTableItemType) {
OWSTableItemTypeAction,
};
typedef void (^OWSTableActionBlock)();
@interface OWSTableItem : NSObject
@property (nonatomic) OWSTableItemType itemType;
@property (nonatomic) NSString *title;
@property (nonatomic) OWSTableActionBlock actionBlock;
@end
#pragma mark -
@implementation OWSTableItem
+ (OWSTableItem *)actionWithTitle:(NSString *)title
actionBlock:(OWSTableActionBlock)actionBlock {
OWSAssert(title.length > 0);
OWSTableItem *item = [OWSTableItem new];
item.itemType = OWSTableItemTypeAction;
item.actionBlock = actionBlock;
item.title = title;
return item;
}
@end
#pragma mark -
NSString * const kDebugUITableCellIdentifier = @"kDebugUITableCellIdentifier";
@interface DebugUITableViewController ()
@property (nonatomic) OWSTableContents *contents;
@end
@implementation DebugUITableViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.navigationController.navigationBar setTranslucent:NO];
}
- (instancetype)init
{
return [super initWithStyle:UITableViewStyleGrouped];
}
- (void)loadView
{
[super loadView];
OWSAssert(self.contents);
self.title = self.contents.title;
OWSAssert(self.tableView);
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kDebugUITableCellIdentifier];
}
- (OWSTableSection *)sectionForIndex:(NSInteger)sectionIndex
{
OWSAssert(self.contents);
OWSAssert(sectionIndex >= 0 && sectionIndex < (NSInteger) self.contents.sections.count);
OWSTableSection *section = self.contents.sections[(NSUInteger) sectionIndex];
return section;
}
- (OWSTableItem *)itemForIndexPath:(NSIndexPath *)indexPath
{
OWSAssert(self.contents);
OWSAssert(indexPath.section >= 0 && indexPath.section < (NSInteger) self.contents.sections.count);
OWSTableSection *section = self.contents.sections[(NSUInteger) indexPath.section];
OWSAssert(indexPath.item >= 0 && indexPath.item < (NSInteger) section.items.count);
OWSTableItem *item = section.items[(NSUInteger) indexPath.item];
return item;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
OWSAssert(self.contents);
OWSAssert(self.contents.sections.count > 0);
return (NSInteger) self.contents.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex {
OWSTableSection *section = [self sectionForIndex:sectionIndex];
OWSAssert(section.items.count > 0);
return (NSInteger) section.items.count;
}
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)sectionIndex
{
OWSTableSection *section = [self sectionForIndex:sectionIndex];
return section.title;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
OWSTableItem *item = [self itemForIndexPath:indexPath];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kDebugUITableCellIdentifier];
OWSAssert(cell);
cell.textLabel.text = item.title;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
OWSTableItem *item = [self itemForIndexPath:indexPath];
if (item.itemType == OWSTableItemTypeAction) {
OWSAssert(item.actionBlock);
item.actionBlock();
}
}
#pragma mark - Logging
+ (NSString *)tag
@ -323,25 +126,6 @@ NSString * const kDebugUITableCellIdentifier = @"kDebugUITableCellIdentifier";
messageSender:messageSender];
}
#pragma mark - Presentation
- (void)presentFromViewController:(UIViewController *)fromViewController {
OWSAssert(fromViewController);
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(donePressed:)];
[fromViewController presentViewController:navigationController
animated:YES
completion:nil];
}
- (void)donePressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,59 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class OWSTableItem;
@class OWSTableSection;
@interface OWSTableContents : NSObject
@property (nonatomic) NSString *title;
- (void)addSection:(OWSTableSection *)section;
@end
#pragma mark -
@interface OWSTableSection : NSObject
@property (nonatomic, nullable) NSString *title;
+ (OWSTableSection *)sectionWithTitle:(NSString *)title items:(NSArray<OWSTableItem *> *)items;
- (void)addItem:(OWSTableItem *)item;
@end
#pragma mark -
typedef NS_ENUM(NSInteger, OWSTableItemType) {
OWSTableItemTypeAction,
};
typedef void (^OWSTableActionBlock)();
@interface OWSTableItem : NSObject
+ (OWSTableItem *)actionWithTitle:(NSString *)title
actionBlock:(OWSTableActionBlock)actionBlock;
@end
#pragma mark -
@interface OWSTableViewController : UITableViewController
@property (nonatomic) OWSTableContents *contents;
#pragma mark - Presentation
- (void)presentFromViewController:(UIViewController *)fromViewController;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,230 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSTableContents ()
@property (nonatomic) NSMutableArray<OWSTableSection *> *sections;
@end
#pragma mark -
@implementation OWSTableContents
- (instancetype)init
{
if (self = [super init]) {
_sections = [NSMutableArray new];
}
return self;
}
- (void)addSection:(OWSTableSection *)section
{
OWSAssert(section);
[_sections addObject:section];
}
@end
#pragma mark -
@interface OWSTableSection ()
@property (nonatomic) NSMutableArray<OWSTableItem *> *items;
@end
#pragma mark -
@implementation OWSTableSection
+ (OWSTableSection *)sectionWithTitle:(NSString *)title items:(NSArray<OWSTableItem *> *)items
{
OWSTableSection *section = [OWSTableSection new];
section.title = title;
section.items = [items mutableCopy];
return section;
}
- (instancetype)init
{
if (self = [super init]) {
_items = [NSMutableArray new];
}
return self;
}
- (void)addItem:(OWSTableItem *)item
{
OWSAssert(item);
[_items addObject:item];
}
@end
#pragma mark -
@interface OWSTableItem ()
@property (nonatomic) OWSTableItemType itemType;
@property (nonatomic, nullable) NSString *title;
@property (nonatomic, nullable) OWSTableActionBlock actionBlock;
@end
#pragma mark -
@implementation OWSTableItem
+ (OWSTableItem *)actionWithTitle:(NSString *)title actionBlock:(OWSTableActionBlock)actionBlock
{
OWSAssert(title.length > 0);
OWSTableItem *item = [OWSTableItem new];
item.itemType = OWSTableItemTypeAction;
item.actionBlock = actionBlock;
item.title = title;
return item;
}
@end
#pragma mark -
NSString * const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
@implementation OWSTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController.navigationBar setTranslucent:NO];
}
- (instancetype)init
{
return [super initWithStyle:UITableViewStyleGrouped];
}
- (void)loadView
{
[super loadView];
OWSAssert(self.contents);
self.title = self.contents.title;
OWSAssert(self.tableView);
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kOWSTableCellIdentifier];
}
- (OWSTableSection *)sectionForIndex:(NSInteger)sectionIndex
{
OWSAssert(self.contents);
OWSAssert(sectionIndex >= 0 && sectionIndex < (NSInteger) self.contents.sections.count);
OWSTableSection *section = self.contents.sections[(NSUInteger) sectionIndex];
return section;
}
- (OWSTableItem *)itemForIndexPath:(NSIndexPath *)indexPath
{
OWSAssert(self.contents);
OWSAssert(indexPath.section >= 0 && indexPath.section < (NSInteger) self.contents.sections.count);
OWSTableSection *section = self.contents.sections[(NSUInteger) indexPath.section];
OWSAssert(indexPath.item >= 0 && indexPath.item < (NSInteger) section.items.count);
OWSTableItem *item = section.items[(NSUInteger) indexPath.item];
return item;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
OWSAssert(self.contents);
OWSAssert(self.contents.sections.count > 0);
return (NSInteger) self.contents.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex
{
OWSTableSection *section = [self sectionForIndex:sectionIndex];
OWSAssert(section.items.count > 0);
return (NSInteger) section.items.count;
}
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)sectionIndex
{
OWSTableSection *section = [self sectionForIndex:sectionIndex];
return section.title;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
OWSTableItem *item = [self itemForIndexPath:indexPath];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kOWSTableCellIdentifier];
OWSAssert(cell);
cell.textLabel.text = item.title;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
OWSTableItem *item = [self itemForIndexPath:indexPath];
if (item.itemType == OWSTableItemTypeAction) {
OWSAssert(item.actionBlock);
item.actionBlock();
}
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
#pragma mark - Presentation
- (void)presentFromViewController:(UIViewController *)fromViewController
{
OWSAssert(fromViewController);
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(donePressed:)];
[fromViewController presentViewController:navigationController
animated:YES
completion:nil];
}
- (void)donePressed:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
NS_ASSUME_NONNULL_END

View file

@ -3,16 +3,17 @@
//
#import "PrivacySettingsTableViewController.h"
#import "BlockListViewController.h"
#import "Environment.h"
#import "PropertyListPreferences.h"
#import "UIUtil.h"
#import "Signal-Swift.h"
#import "UIUtil.h"
#import <25519/Curve25519.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) {
PrivacySettingsTableViewControllerSectionIndexBlockList,
PrivacySettingsTableViewControllerSectionIndexScreenSecurity,
PrivacySettingsTableViewControllerSectionIndexCalling,
PrivacySettingsTableViewControllerSectionIndexCallKit,
@ -23,6 +24,8 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) {
@interface PrivacySettingsTableViewController ()
@property (nonatomic) UITableViewCell *blocklistCell;
@property (nonatomic) UITableViewCell *enableCallKitCell;
@property (nonatomic) UISwitch *enableCallKitSwitch;
@ -60,6 +63,12 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) {
self.title = NSLocalizedString(@"SETTINGS_PRIVACY_TITLE", @"");
// Block List
self.blocklistCell = [UITableViewCell new];
self.blocklistCell.textLabel.text
= NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE", @"Label for the block list section of the settings view");
self.blocklistCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// CallKit opt-out
self.enableCallKitCell = [UITableViewCell new];
self.enableCallKitCell.textLabel.text = NSLocalizedString(@"SETTINGS_PRIVACY_CALLKIT_TITLE", @"Short table cell label");
@ -127,6 +136,8 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) {
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case PrivacySettingsTableViewControllerSectionIndexBlockList:
return 1;
case PrivacySettingsTableViewControllerSectionIndexScreenSecurity:
return 1;
case PrivacySettingsTableViewControllerSectionIndexCalling:
@ -167,6 +178,8 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) {
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case PrivacySettingsTableViewControllerSectionIndexBlockList:
return self.blocklistCell;
case PrivacySettingsTableViewControllerSectionIndexScreenSecurity:
return self.enableScreenSecurityCell;
case PrivacySettingsTableViewControllerSectionIndexCalling:
@ -209,6 +222,13 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
switch (indexPath.section) {
case PrivacySettingsTableViewControllerSectionIndexBlockList: {
BlockListViewController *vc = [[BlockListViewController alloc] init];
NSAssert(self.navigationController != nil, @"Navigation controller must not be nil");
NSAssert(vc != nil, @"About View Controller must not be nil");
[self.navigationController pushViewController:vc animated:YES];
break;
}
case PrivacySettingsTableViewControllerSectionIndexHistoryLog: {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil
message:NSLocalizedString(@"SETTINGS_DELETE_HISTORYLOG_CONFIRMATION", @"Alert message before user confirms clearing history")

View file

@ -9,17 +9,20 @@
#import "PhoneNumberUtil.h"
#import "SignalKeyingStorage.h"
#import "TSAccountManager.h"
#import "Util.h"
#import "UIView+OWS.h"
#import "Util.h"
#import "ViewControllerUtils.h"
static NSString *const kCodeSentSegue = @"codeSent";
@interface RegistrationViewController ()
@property (nonatomic) NSString *lastCallingCode;
@property (nonatomic) NSString *callingCode;
@end
#pragma mark -
@implementation RegistrationViewController
- (void)viewDidLoad {
@ -32,11 +35,14 @@ static NSString *const kCodeSentSegue = @"codeSent";
[[Environment getCurrent] setSignUpFlowNavigationController:self.navigationController];
_titleLabel.text = NSLocalizedString(@"REGISTRATION_TITLE_LABEL", @"");
[_countryNameButton setTitle:NSLocalizedString(@"REGISTRATION_DEFAULT_COUNTRY_NAME", @"")
forState:UIControlStateNormal];
_phoneNumberTextField.placeholder = NSLocalizedString(@"REGISTRATION_ENTERNUMBER_DEFAULT_TEXT", @"");
[_phoneNumberButton setTitle:NSLocalizedString(@"REGISTRATION_PHONENUMBER_BUTTON", @"")
forState:UIControlStateNormal];
[_countryNameButton
setTitle:NSLocalizedString(@"REGISTRATION_DEFAULT_COUNTRY_NAME", @"Label for the country code field")
forState:UIControlStateNormal];
_phoneNumberTextField.placeholder = NSLocalizedString(
@"REGISTRATION_ENTERNUMBER_DEFAULT_TEXT", @"Placeholder text for the phone number textfield");
[_phoneNumberButton
setTitle:NSLocalizedString(@"REGISTRATION_PHONENUMBER_BUTTON", @"Label for the phone number textfield")
forState:UIControlStateNormal];
[_phoneNumberButton.titleLabel setAdjustsFontSizeToFitWidth:YES];
[_sendCodeButton setTitle:NSLocalizedString(@"REGISTRATION_VERIFY_DEVICE", @"") forState:UIControlStateNormal];
[_existingUserButton setTitle:NSLocalizedString(@"ALREADY_HAVE_ACCOUNT_BUTTON", @"registration button text")
@ -73,7 +79,7 @@ static NSString *const kCodeSentSegue = @"codeSent";
callingCode:(NSString *)callingCode
countryCode:(NSString *)countryCode {
_lastCallingCode = callingCode;
_callingCode = callingCode;
NSString *title = [NSString stringWithFormat:@"%@ (%@)",
callingCode,
@ -128,7 +134,7 @@ static NSString *const kCodeSentSegue = @"codeSent";
}
- (IBAction)sendCodeAction:(id)sender {
NSString *phoneNumber = [NSString stringWithFormat:@"%@%@", _lastCallingCode, _phoneNumberTextField.text];
NSString *phoneNumber = [NSString stringWithFormat:@"%@%@", _callingCode, _phoneNumberTextField.text];
PhoneNumber *localNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber];
[_sendCodeButton setEnabled:NO];
@ -187,50 +193,12 @@ static NSString *const kCodeSentSegue = @"codeSent";
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText {
// Phone numbers takes many forms.
//
// * We only want to let the user enter decimal digits.
// * The user shouldn't have to enter hyphen, parentheses or whitespace;
// the phone number should be formatted automatically.
// * The user should be able to copy and paste freely.
// * Invalid input should be simply ignored.
//
// We accomplish this by being permissive and trying to "take as much of the user
// input as possible".
//
// * Always accept deletes.
// * Ignore invalid input.
// * Take partial input if possible.
NSString *oldText = textField.text;
// Construct the new contents of the text field by:
// 1. Determining the "left" substring: the contents of the old text _before_ the deletion range.
// Filtering will remove non-decimal digit characters like hyphen "-".
NSString *left = [oldText substringToIndex:range.location].digitsOnly;
// 2. Determining the "right" substring: the contents of the old text _after_ the deletion range.
NSString *right = [oldText substringFromIndex:range.location + range.length].digitsOnly;
// 3. Determining the "center" substring: the contents of the new insertion text.
NSString *center = insertionText.digitsOnly;
// 4. Construct the "raw" new text by concatenating left, center and right.
NSString *textAfterChange = [[left stringByAppendingString:center]
stringByAppendingString:right];
// 5. Construct the "formatted" new text by inserting a hyphen if necessary.
// reformat the phone number, trying to keep the cursor beside the inserted or deleted digit
bool isJustDeletion = insertionText.length == 0;
NSUInteger cursorPositionAfterChange = left.length + center.length;
NSString *textAfterReformat =
[PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:textAfterChange
withSpecifiedCountryCodeString:_countryCodeButton.titleLabel.text];
NSUInteger cursorPositionAfterReformat = [PhoneNumberUtil translateCursorPosition:cursorPositionAfterChange
from:textAfterChange
to:textAfterReformat
stickingRightward:isJustDeletion];
textField.text = textAfterReformat;
UITextPosition *pos =
[textField positionFromPosition:textField.beginningOfDocument offset:(NSInteger)cursorPositionAfterReformat];
[textField setSelectedTextRange:[textField textRangeFromPosition:pos toPosition:pos]];
[ViewControllerUtils phoneNumberTextField:textField
shouldChangeCharactersInRange:range
replacementString:insertionText
countryCode:_callingCode];
return NO; // inform our caller that we took care of performing the change
}

View file

@ -1,9 +1,5 @@
//
// SettingsTableViewController.h
// Signal
//
// Created by Dylan Bourgeois on 03/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>

View file

@ -0,0 +1,19 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ViewControllerUtils : NSObject
- (instancetype)init NS_UNAVAILABLE;
// This convenience function can be used to reformat the contents of
// a phone number text field as the user modifies its text by typing,
// pasting, etc.
+ (void)phoneNumberTextField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
countryCode:(NSString *)countryCode;
@end

View file

@ -0,0 +1,73 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "ViewControllerUtils.h"
#import "PhoneNumber.h"
#import "StringUtil.h"
#import <SignalServiceKit/PhoneNumberUtil.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@implementation ViewControllerUtils
+ (void)phoneNumberTextField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
countryCode:(NSString *)countryCode
{
// Phone numbers takes many forms.
//
// * We only want to let the user enter decimal digits.
// * The user shouldn't have to enter hyphen, parentheses or whitespace;
// the phone number should be formatted automatically.
// * The user should be able to copy and paste freely.
// * Invalid input should be simply ignored.
//
// We accomplish this by being permissive and trying to "take as much of the user
// input as possible".
//
// * Always accept deletes.
// * Ignore invalid input.
// * Take partial input if possible.
NSString *oldText = textField.text;
// Construct the new contents of the text field by:
// 1. Determining the "left" substring: the contents of the old text _before_ the deletion range.
// Filtering will remove non-decimal digit characters like hyphen "-".
NSString *left = [oldText substringToIndex:range.location].digitsOnly;
// 2. Determining the "right" substring: the contents of the old text _after_ the deletion range.
NSString *right = [oldText substringFromIndex:range.location + range.length].digitsOnly;
// 3. Determining the "center" substring: the contents of the new insertion text.
NSString *center = insertionText.digitsOnly;
// 4. Construct the "raw" new text by concatenating left, center and right.
NSString *textAfterChange = [[left stringByAppendingString:center] stringByAppendingString:right];
// 4a. Ensure we don't exceed the maximum length for a e164 phone number,
// 15 digits, per: https://en.wikipedia.org/wiki/E.164
const int kMaxPhoneNumberLength = 15;
if (textAfterChange.length > kMaxPhoneNumberLength) {
textAfterChange = [textAfterChange substringToIndex:kMaxPhoneNumberLength];
}
// 5. Construct the "formatted" new text by inserting a hyphen if necessary.
// reformat the phone number, trying to keep the cursor beside the inserted or deleted digit
bool isJustDeletion = insertionText.length == 0;
NSUInteger cursorPositionAfterChange = MIN(left.length + center.length, textAfterChange.length);
NSString *textAfterReformat =
[PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:textAfterChange
withSpecifiedCountryCodeString:countryCode];
NSUInteger cursorPositionAfterReformat = [PhoneNumberUtil translateCursorPosition:cursorPositionAfterChange
from:textAfterChange
to:textAfterReformat
stickingRightward:isJustDeletion];
textField.text = textAfterReformat;
UITextPosition *pos =
[textField positionFromPosition:textField.beginningOfDocument offset:(NSInteger)cursorPositionAfterReformat];
[textField setSelectedTextRange:[textField textRangeFromPosition:pos toPosition:pos]];
}
@end
NS_ASSUME_NONNULL_END

View file

@ -97,11 +97,26 @@
/* No comment provided by engineer. */
"ATTACHMENT_QUEUED" = "New attachment queued for retrieval.";
/* A message indicating that an attachment of unknown type was received. */
"ATTACHMENT_UNKNOWN_TYPE" = "Unknown attachment received";
/* Button label for the 'unblock' button */
"BLOCK_LIST_UNBLOCK_BUTTON" = "Unblock";
/* No comment provided by engineer. */
"AUDIO_PERMISSION_MESSAGE" = "Signal requires access to your microphone to work properly. You can grant this permission in the Settings app >> Privacy >> Microphone >> Signal";
/* A format for the 'unblock phone number' action sheet title. */
"BLOCK_LIST_UNBLOCK_TITLE_FORMAT" = "Unblock %@?";
/* A label for the block button in the block list view */
"BLOCK_LIST_VIEW_BLOCK_BUTTON" = "Block";
/* 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.";
/* The title of the 'phone number blocked' alert in the block view. */
"BLOCK_LIST_VIEW_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.";
/* The title of the 'phone number unblocked' alert in the block view. */
"BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE" = "Phone Number Unblocked";
/* Accessibilty label for placing call button */
"CALL_LABEL" = "Call";
@ -790,6 +805,9 @@
/* Navbar title */
"SETTINGS_ABOUT" = "About";
/* No comment provided by engineer. */
"SETTINGS_ADD_TO_BLOCK_LIST_TITLE" = "Add to Block List";
/* No comment provided by engineer. */
"SETTINGS_ADVANCED_DEBUGLOG" = "Enable Debug Log";
@ -799,6 +817,15 @@
/* No comment provided by engineer. */
"SETTINGS_ADVANCED_TITLE" = "Advanced";
/* A label for the 'add phone number' button in the block list table. */
"SETTINGS_BLOCK_LIST_ADD_BUTTON" = "Add Phone Number";
/* A header title for the block list table. */
"SETTINGS_BLOCK_LIST_HEADER_TITLE" = "Blocked Phone Numbers";
/* Label for the block list row of the settings view */
"SETTINGS_BLOCK_LIST_TITLE" = "Blocked";
/* User settings section footer, a detailed explanation */
"SETTINGS_BLOCK_ON_IDENITY_CHANGE_DETAIL" = "Requires your approval before communicating with someone who has a new safety number, commonly from a reinstall of Signal.";