mirror of https://github.com/oxen-io/session-ios
Merge branch 'charlesmchen/onboardingRemoveOldViews'
commit
1ec1a9aa63
@ -1,15 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWS2FARegistrationViewController : OWSViewController
|
||||
|
||||
@property (nonatomic) NSString *verificationCode;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,161 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS2FARegistrationViewController.h"
|
||||
#import "PinEntryView.h"
|
||||
#import "ProfileViewController.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <PromiseKit/AnyPromise.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SignalMessaging/UIViewController+OWS.h>
|
||||
#import <SignalServiceKit/OWS2FAManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWS2FARegistrationViewController () <PinEntryViewDelegate>
|
||||
|
||||
@property (nonatomic) PinEntryView *entryView;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWS2FARegistrationViewController
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (AccountManager *)accountManager
|
||||
{
|
||||
return AppEnvironment.shared.accountManager;
|
||||
}
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
// The navigation bar is hidden in the registration workflow.
|
||||
if (self.navigationController.navigationBarHidden) {
|
||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
||||
}
|
||||
self.navigationItem.hidesBackButton = YES;
|
||||
|
||||
self.title = NSLocalizedString(@"REGISTRATION_ENTER_LOCK_PIN_NAV_TITLE",
|
||||
@"Navigation title shown when user is re-registering after having enabled registration lock");
|
||||
|
||||
self.view.backgroundColor = [Theme backgroundColor];
|
||||
|
||||
PinEntryView *entryView = [PinEntryView new];
|
||||
self.entryView = entryView;
|
||||
entryView.delegate = self;
|
||||
[self.view addSubview:entryView];
|
||||
|
||||
entryView.instructionsText = NSLocalizedString(
|
||||
@"REGISTER_2FA_INSTRUCTIONS", @"Instructions to enter the 'two-factor auth pin' in the 2FA registration view.");
|
||||
|
||||
// Layout
|
||||
[entryView autoPinToTopLayoutGuideOfViewController:self withInset:0];
|
||||
[entryView autoPinEdgeToSuperviewMargin:ALEdgeLeft];
|
||||
[entryView autoPinEdgeToSuperviewMargin:ALEdgeRight];
|
||||
[entryView autoPinToBottomLayoutGuideOfViewController:self withInset:0];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[self.entryView makePinTextFieldFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - PinEntryViewDelegate
|
||||
|
||||
- (void)pinEntryView:(PinEntryView *)entryView submittedPinCode:(NSString *)pinCode
|
||||
{
|
||||
OWSAssertDebug(self.entryView.hasValidPin);
|
||||
|
||||
[self tryToRegisterWithPinCode:pinCode];
|
||||
}
|
||||
|
||||
- (void)pinEntryViewForgotPinLinkTapped:(PinEntryView *)entryView
|
||||
{
|
||||
NSString *alertBody = NSLocalizedString(@"REGISTER_2FA_FORGOT_PIN_ALERT_MESSAGE",
|
||||
@"Alert message explaining what happens if you forget your 'two-factor auth pin'.");
|
||||
[OWSAlerts showAlertWithTitle:nil message:alertBody];
|
||||
}
|
||||
|
||||
#pragma mark - Registration
|
||||
|
||||
- (void)tryToRegisterWithPinCode:(NSString *)pinCode
|
||||
{
|
||||
OWSAssertDebug(self.entryView.hasValidPin);
|
||||
OWSAssertDebug(self.verificationCode.length > 0);
|
||||
OWSAssertDebug(pinCode.length > 0);
|
||||
|
||||
OWSLogInfo(@"");
|
||||
|
||||
__weak OWS2FARegistrationViewController *weakSelf = self;
|
||||
|
||||
[ModalActivityIndicatorViewController
|
||||
presentFromViewController:self
|
||||
canCancel:NO
|
||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteringCode]);
|
||||
[[self.accountManager registerObjcWithVerificationCode:self.verificationCode pin:pinCode]
|
||||
.then(^{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteringSubmittedCode]);
|
||||
[[OWS2FAManager sharedManager] mark2FAAsEnabledWithPin:pinCode];
|
||||
|
||||
OWSLogInfo(@"Successfully registered Signal account.");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
[weakSelf verificationWasCompleted];
|
||||
}];
|
||||
});
|
||||
})
|
||||
.catch(^(NSError *error) {
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegistrationFailed]);
|
||||
OWSLogError(@"error verifying challenge: %@", error);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
[OWSAlerts
|
||||
showAlertWithTitle:NSLocalizedString(
|
||||
@"REGISTER_2FA_REGISTRATION_FAILED_ALERT_TITLE",
|
||||
@"Title for alert indicating that attempt to "
|
||||
@"register with 'two-factor auth' failed.")
|
||||
message:error.localizedDescription];
|
||||
|
||||
[weakSelf.entryView makePinTextFieldFirstResponder];
|
||||
}];
|
||||
});
|
||||
}) retainUntilComplete];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)verificationWasCompleted
|
||||
{
|
||||
[RegistrationController verificationWasCompletedFromView:self];
|
||||
}
|
||||
|
||||
#pragma mark - Orientation
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,15 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CodeVerificationViewController : OWSViewController
|
||||
|
||||
- (void)setVerificationCodeAndTryToVerify:(NSString *)verificationCode;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,509 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CodeVerificationViewController.h"
|
||||
#import "OWS2FARegistrationViewController.h"
|
||||
#import "ProfileViewController.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <PromiseKit/AnyPromise.h>
|
||||
#import <SignalMessaging/UIViewController+OWS.h>
|
||||
#import <SignalServiceKit/OWSError.h>
|
||||
#import <SignalServiceKit/TSAccountManager.h>
|
||||
#import <SignalServiceKit/TSNetworkManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CodeVerificationViewController () <UITextFieldDelegate>
|
||||
|
||||
// Where the user enters the verification code they wish to document
|
||||
@property (nonatomic) UITextField *challengeTextField;
|
||||
|
||||
@property (nonatomic) UILabel *phoneNumberLabel;
|
||||
|
||||
//// User action buttons
|
||||
@property (nonatomic) OWSFlatButton *submitButton;
|
||||
@property (nonatomic) UIButton *sendCodeViaSMSAgainButton;
|
||||
@property (nonatomic) UIButton *sendCodeViaVoiceButton;
|
||||
|
||||
@property (nonatomic) UIActivityIndicatorView *submitCodeSpinner;
|
||||
@property (nonatomic) UIActivityIndicatorView *requestCodeAgainSpinner;
|
||||
@property (nonatomic) UIActivityIndicatorView *requestCallSpinner;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CodeVerificationViewController
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (TSAccountManager *)tsAccountManager
|
||||
{
|
||||
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
|
||||
|
||||
return SSKEnvironment.shared.tsAccountManager;
|
||||
}
|
||||
|
||||
- (AccountManager *)accountManager
|
||||
{
|
||||
return AppEnvironment.shared.accountManager;
|
||||
}
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.shouldUseTheme = NO;
|
||||
|
||||
[self createViews];
|
||||
|
||||
[self initializeKeyboardHandlers];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
[self enableServerActions:YES];
|
||||
[self updatePhoneNumberLabel];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
[_challengeTextField becomeFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)createViews
|
||||
{
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
self.view.opaque = YES;
|
||||
|
||||
UIColor *signalBlueColor = [UIColor ows_signalBrandBlueColor];
|
||||
|
||||
UIView *header = [UIView new];
|
||||
header.backgroundColor = signalBlueColor;
|
||||
[self.view addSubview:header];
|
||||
[header autoPinWidthToSuperview];
|
||||
[header autoPinEdgeToSuperviewEdge:ALEdgeTop];
|
||||
// The header will grow to accomodate the titleLabel's height.
|
||||
|
||||
UILabel *titleLabel = [UILabel new];
|
||||
titleLabel.textColor = [UIColor whiteColor];
|
||||
titleLabel.text = [self phoneNumberText];
|
||||
titleLabel.font = [UIFont ows_mediumFontWithSize:20.f];
|
||||
[header addSubview:titleLabel];
|
||||
[titleLabel autoPinToTopLayoutGuideOfViewController:self withInset:0];
|
||||
[titleLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
[titleLabel autoSetDimension:ALDimensionHeight toSize:40];
|
||||
[titleLabel autoHCenterInSuperview];
|
||||
|
||||
// This view is used in more than one context.
|
||||
//
|
||||
// * Usually, it is pushed atop RegistrationViewController in which
|
||||
// case we want a "back" button.
|
||||
// * It can also be used to re-register from the app's "de-registration"
|
||||
// views, in which case RegistrationViewController is not used and we
|
||||
// do _not_ want a "back" button.
|
||||
if (self.navigationController.viewControllers.count > 1) {
|
||||
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[backButton
|
||||
setTitle:NSLocalizedString(@"VERIFICATION_BACK_BUTTON", @"button text for back button on verification view")
|
||||
forState:UIControlStateNormal];
|
||||
[backButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
backButton.titleLabel.font = [UIFont ows_mediumFontWithSize:14.f];
|
||||
[header addSubview:backButton];
|
||||
[backButton autoPinLeadingToSuperviewMarginWithInset:10.f];
|
||||
[backButton autoAlignAxis:ALAxisHorizontal toSameAxisOfView:titleLabel];
|
||||
[backButton addTarget:self action:@selector(backButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
|
||||
_phoneNumberLabel = [UILabel new];
|
||||
_phoneNumberLabel.textColor = [UIColor ows_darkGrayColor];
|
||||
_phoneNumberLabel.font = [UIFont ows_regularFontWithSize:20.f];
|
||||
_phoneNumberLabel.numberOfLines = 2;
|
||||
_phoneNumberLabel.adjustsFontSizeToFitWidth = YES;
|
||||
_phoneNumberLabel.textAlignment = NSTextAlignmentCenter;
|
||||
[self.view addSubview:_phoneNumberLabel];
|
||||
[_phoneNumberLabel autoPinWidthToSuperviewWithMargin:ScaleFromIPhone5(32)];
|
||||
[_phoneNumberLabel autoPinEdge:ALEdgeTop
|
||||
toEdge:ALEdgeBottom
|
||||
ofView:header
|
||||
withOffset:ScaleFromIPhone5To7Plus(30, 100)];
|
||||
|
||||
const CGFloat kHMargin = 36;
|
||||
|
||||
if (UIDevice.currentDevice.isShorterThanIPhone5) {
|
||||
_challengeTextField = [DismissableTextField new];
|
||||
} else {
|
||||
_challengeTextField = [OWSTextField new];
|
||||
}
|
||||
|
||||
_challengeTextField.textColor = [UIColor blackColor];
|
||||
_challengeTextField.placeholder = NSLocalizedString(@"VERIFICATION_CHALLENGE_DEFAULT_TEXT",
|
||||
@"Text field placeholder for SMS verification code during registration");
|
||||
_challengeTextField.font = [UIFont ows_lightFontWithSize:21.f];
|
||||
_challengeTextField.textAlignment = NSTextAlignmentCenter;
|
||||
_challengeTextField.keyboardType = UIKeyboardTypeNumberPad;
|
||||
_challengeTextField.delegate = self;
|
||||
[self.view addSubview:_challengeTextField];
|
||||
[_challengeTextField autoPinWidthToSuperviewWithMargin:kHMargin];
|
||||
[_challengeTextField autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:_phoneNumberLabel withOffset:25];
|
||||
|
||||
UIView *underscoreView = [UIView new];
|
||||
underscoreView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:1.f];
|
||||
[self.view addSubview:underscoreView];
|
||||
[underscoreView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:_challengeTextField withOffset:3];
|
||||
[underscoreView autoPinWidthToSuperviewWithMargin:kHMargin];
|
||||
[underscoreView autoSetDimension:ALDimensionHeight toSize:1.f];
|
||||
|
||||
const CGFloat kSubmitButtonHeight = 47.f;
|
||||
// NOTE: We use ows_signalBrandBlueColor instead of ows_materialBlueColor
|
||||
// throughout the onboarding flow to be consistent with the headers.
|
||||
OWSFlatButton *submitButton =
|
||||
[OWSFlatButton buttonWithTitle:NSLocalizedString(@"VERIFICATION_CHALLENGE_SUBMIT_CODE",
|
||||
@"button text during registration to submit your SMS verification code.")
|
||||
font:[OWSFlatButton fontForHeight:kSubmitButtonHeight]
|
||||
titleColor:[UIColor whiteColor]
|
||||
backgroundColor:[UIColor ows_signalBrandBlueColor]
|
||||
target:self
|
||||
selector:@selector(submitVerificationCode)];
|
||||
self.submitButton = submitButton;
|
||||
[self.view addSubview:_submitButton];
|
||||
[_submitButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:underscoreView withOffset:15];
|
||||
[_submitButton autoPinWidthToSuperviewWithMargin:kHMargin];
|
||||
[_submitButton autoSetDimension:ALDimensionHeight toSize:kSubmitButtonHeight];
|
||||
|
||||
const CGFloat kSpinnerSize = 20;
|
||||
const CGFloat kSpinnerSpacing = ScaleFromIPhone5To7Plus(5, 15);
|
||||
|
||||
_submitCodeSpinner =
|
||||
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
|
||||
[_submitButton addSubview:_submitCodeSpinner];
|
||||
[_submitCodeSpinner autoSetDimension:ALDimensionWidth toSize:kSpinnerSize];
|
||||
[_submitCodeSpinner autoSetDimension:ALDimensionHeight toSize:kSpinnerSize];
|
||||
[_submitCodeSpinner autoVCenterInSuperview];
|
||||
[_submitCodeSpinner autoPinTrailingToSuperviewMarginWithInset:kSpinnerSpacing];
|
||||
|
||||
_sendCodeViaSMSAgainButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_sendCodeViaSMSAgainButton.backgroundColor = [UIColor whiteColor];
|
||||
[_sendCodeViaSMSAgainButton setTitle:NSLocalizedString(@"VERIFICATION_CHALLENGE_SUBMIT_AGAIN",
|
||||
@"button text during registration to request another SMS code be sent")
|
||||
forState:UIControlStateNormal];
|
||||
[_sendCodeViaSMSAgainButton setTitleColor:signalBlueColor forState:UIControlStateNormal];
|
||||
_sendCodeViaSMSAgainButton.titleLabel.font = [UIFont ows_mediumFontWithSize:14.f];
|
||||
[_sendCodeViaSMSAgainButton addTarget:self
|
||||
action:@selector(sendCodeViaSMSAction:)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:_sendCodeViaSMSAgainButton];
|
||||
[_sendCodeViaSMSAgainButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:_submitButton withOffset:10];
|
||||
[_sendCodeViaSMSAgainButton autoPinWidthToSuperviewWithMargin:kHMargin];
|
||||
[_sendCodeViaSMSAgainButton autoSetDimension:ALDimensionHeight toSize:35];
|
||||
|
||||
_requestCodeAgainSpinner =
|
||||
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
[_sendCodeViaSMSAgainButton addSubview:_requestCodeAgainSpinner];
|
||||
[_requestCodeAgainSpinner autoSetDimension:ALDimensionWidth toSize:kSpinnerSize];
|
||||
[_requestCodeAgainSpinner autoSetDimension:ALDimensionHeight toSize:kSpinnerSize];
|
||||
[_requestCodeAgainSpinner autoVCenterInSuperview];
|
||||
[_requestCodeAgainSpinner autoPinTrailingToSuperviewMarginWithInset:kSpinnerSpacing];
|
||||
|
||||
_sendCodeViaVoiceButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_sendCodeViaVoiceButton.backgroundColor = [UIColor whiteColor];
|
||||
[_sendCodeViaVoiceButton
|
||||
setTitle:NSLocalizedString(@"VERIFICATION_CHALLENGE_SEND_VIA_VOICE",
|
||||
@"button text during registration to request phone number verification be done via phone call")
|
||||
forState:UIControlStateNormal];
|
||||
[_sendCodeViaVoiceButton setTitleColor:signalBlueColor forState:UIControlStateNormal];
|
||||
_sendCodeViaVoiceButton.titleLabel.font = [UIFont ows_mediumFontWithSize:14.f];
|
||||
[_sendCodeViaVoiceButton addTarget:self
|
||||
action:@selector(sendCodeViaVoiceAction:)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:_sendCodeViaVoiceButton];
|
||||
[_sendCodeViaVoiceButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:_sendCodeViaSMSAgainButton];
|
||||
[_sendCodeViaVoiceButton autoPinWidthToSuperviewWithMargin:kHMargin];
|
||||
[_sendCodeViaVoiceButton autoSetDimension:ALDimensionHeight toSize:35];
|
||||
|
||||
_requestCallSpinner =
|
||||
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
[_sendCodeViaVoiceButton addSubview:_requestCallSpinner];
|
||||
[_requestCallSpinner autoSetDimension:ALDimensionWidth toSize:kSpinnerSize];
|
||||
[_requestCallSpinner autoSetDimension:ALDimensionHeight toSize:kSpinnerSize];
|
||||
[_requestCallSpinner autoVCenterInSuperview];
|
||||
[_requestCallSpinner autoPinTrailingToSuperviewMarginWithInset:kSpinnerSpacing];
|
||||
}
|
||||
|
||||
- (NSString *)phoneNumberText
|
||||
{
|
||||
OWSAssertDebug([TSAccountManager localNumber] != nil);
|
||||
return [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:[TSAccountManager localNumber]];
|
||||
}
|
||||
|
||||
- (void)updatePhoneNumberLabel
|
||||
{
|
||||
_phoneNumberLabel.text =
|
||||
[NSString stringWithFormat:NSLocalizedString(@"VERIFICATION_PHONE_NUMBER_FORMAT",
|
||||
@"Label indicating the phone number currently being verified."),
|
||||
[self phoneNumberText]];
|
||||
}
|
||||
|
||||
- (void)startActivityIndicator
|
||||
{
|
||||
[self.submitCodeSpinner startAnimating];
|
||||
[self enableServerActions:NO];
|
||||
[self.challengeTextField resignFirstResponder];
|
||||
}
|
||||
|
||||
- (void)stopActivityIndicator
|
||||
{
|
||||
[self enableServerActions:YES];
|
||||
[self.submitCodeSpinner stopAnimating];
|
||||
}
|
||||
|
||||
- (void)submitVerificationCode
|
||||
{
|
||||
[self startActivityIndicator];
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteringCode]);
|
||||
__weak CodeVerificationViewController *weakSelf = self;
|
||||
[[self.accountManager registerObjcWithVerificationCode:[self validationCodeFromTextField] pin:nil]
|
||||
.then(^{
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteringSubmittedCode]);
|
||||
|
||||
OWSLogInfo(@"Successfully registered Signal account.");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf stopActivityIndicator];
|
||||
[weakSelf verificationWasCompleted];
|
||||
});
|
||||
})
|
||||
.catch(^(NSError *error) {
|
||||
OWSLogError(@"error: %@, %@, %zd", [error class], error.domain, error.code);
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegistrationFailed]);
|
||||
OWSLogError(@"error verifying challenge: %@", error);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf stopActivityIndicator];
|
||||
|
||||
if ([error.domain isEqualToString:OWSSignalServiceKitErrorDomain]
|
||||
&& error.code == OWSErrorCodeRegistrationMissing2FAPIN) {
|
||||
CodeVerificationViewController *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
OWSLogInfo(@"Showing 2FA registration view.");
|
||||
OWS2FARegistrationViewController *viewController = [OWS2FARegistrationViewController new];
|
||||
viewController.verificationCode = strongSelf.validationCodeFromTextField;
|
||||
[strongSelf.navigationController pushViewController:viewController animated:YES];
|
||||
} else {
|
||||
[weakSelf presentAlertWithVerificationError:error];
|
||||
[weakSelf.challengeTextField becomeFirstResponder];
|
||||
}
|
||||
});
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
|
||||
- (void)verificationWasCompleted
|
||||
{
|
||||
[RegistrationController verificationWasCompletedFromView:self];
|
||||
}
|
||||
|
||||
- (void)presentAlertWithVerificationError:(NSError *)error
|
||||
{
|
||||
UIAlertController *alert;
|
||||
alert = [UIAlertController
|
||||
alertControllerWithTitle:NSLocalizedString(@"REGISTRATION_VERIFICATION_FAILED_TITLE", @"Alert view title")
|
||||
message:error.localizedDescription
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:CommonStrings.dismissButton
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self.challengeTextField becomeFirstResponder];
|
||||
}]];
|
||||
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (NSString *)validationCodeFromTextField
|
||||
{
|
||||
return [self.challengeTextField.text stringByReplacingOccurrencesOfString:@"-" withString:@""];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)sendCodeViaSMSAction:(id)sender
|
||||
{
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteringRequestedNewCodeBySms]);
|
||||
|
||||
[self enableServerActions:NO];
|
||||
|
||||
[_requestCodeAgainSpinner startAnimating];
|
||||
__weak CodeVerificationViewController *weakSelf = self;
|
||||
[self.tsAccountManager rerequestSMSWithCaptchaToken:nil
|
||||
success:^{
|
||||
OWSLogInfo(@"Successfully requested SMS code");
|
||||
[weakSelf enableServerActions:YES];
|
||||
[weakSelf.requestCodeAgainSpinner stopAnimating];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
OWSLogError(@"Failed to request SMS code with error: %@", error);
|
||||
[weakSelf showRegistrationErrorMessage:error];
|
||||
[weakSelf enableServerActions:YES];
|
||||
[weakSelf.requestCodeAgainSpinner stopAnimating];
|
||||
[weakSelf.challengeTextField becomeFirstResponder];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)sendCodeViaVoiceAction:(id)sender
|
||||
{
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteringRequestedNewCodeByVoice]);
|
||||
|
||||
[self enableServerActions:NO];
|
||||
|
||||
[_requestCallSpinner startAnimating];
|
||||
__weak CodeVerificationViewController *weakSelf = self;
|
||||
[self.tsAccountManager rerequestVoiceWithCaptchaToken:nil
|
||||
success:^{
|
||||
OWSLogInfo(@"Successfully requested voice code");
|
||||
|
||||
[weakSelf enableServerActions:YES];
|
||||
[weakSelf.requestCallSpinner stopAnimating];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
OWSLogError(@"Failed to request voice code with error: %@", error);
|
||||
[weakSelf showRegistrationErrorMessage:error];
|
||||
[weakSelf enableServerActions:YES];
|
||||
[weakSelf.requestCallSpinner stopAnimating];
|
||||
[weakSelf.challengeTextField becomeFirstResponder];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showRegistrationErrorMessage:(NSError *)registrationError
|
||||
{
|
||||
[OWSAlerts showAlertWithTitle:registrationError.localizedDescription
|
||||
message:registrationError.localizedRecoverySuggestion];
|
||||
}
|
||||
|
||||
- (void)enableServerActions:(BOOL)enabled
|
||||
{
|
||||
[_submitButton setEnabled:enabled];
|
||||
[_sendCodeViaSMSAgainButton setEnabled:enabled];
|
||||
[_sendCodeViaVoiceButton setEnabled:enabled];
|
||||
}
|
||||
|
||||
- (void)backButtonPressed:(id)sender
|
||||
{
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationVerificationBack]);
|
||||
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard notifications
|
||||
|
||||
- (void)initializeKeyboardHandlers
|
||||
{
|
||||
UITapGestureRecognizer *outsideTabRecognizer =
|
||||
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboardFromAppropriateSubView)];
|
||||
[self.view addGestureRecognizer:outsideTabRecognizer];
|
||||
self.view.userInteractionEnabled = YES;
|
||||
}
|
||||
|
||||
- (void)dismissKeyboardFromAppropriateSubView
|
||||
{
|
||||
[self.view endEditing:NO];
|
||||
}
|
||||
|
||||
- (BOOL)textField:(UITextField *)textField
|
||||
shouldChangeCharactersInRange:(NSRange)range
|
||||
replacementString:(NSString *)insertionText
|
||||
{
|
||||
|
||||
// Verification codes take this form: "123-456".
|
||||
//
|
||||
// * We only want to let the user "6 decimal digits + 1 hyphen = 7".
|
||||
// * The user shouldn't have to enter the hyphen - it should be added automatically.
|
||||
// * The user should be able to copy and paste freely.
|
||||
// * Invalid input (including extraneous hyphens) 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;
|
||||
// 3a. Trim the tail of the "center" substring to ensure that we don't end up
|
||||
// with more than 6 decimal digits.
|
||||
while (center.length > 0 && left.length + center.length + right.length > 6) {
|
||||
center = [center substringToIndex:center.length - 1];
|
||||
}
|
||||
// 4. Construct the "raw" new text by concatenating left, center and right.
|
||||
NSString *rawNewText = [[left stringByAppendingString:center] stringByAppendingString:right];
|
||||
// 5. Construct the "formatted" new text by inserting a hyphen if necessary.
|
||||
NSString *formattedNewText
|
||||
= (rawNewText.length <= 3 ? rawNewText
|
||||
: [[[rawNewText substringToIndex:3] stringByAppendingString:@"-"]
|
||||
stringByAppendingString:[rawNewText substringFromIndex:3]]);
|
||||
textField.text = formattedNewText;
|
||||
|
||||
// Move the cursor after the newly inserted text.
|
||||
NSUInteger newInsertionPoint = left.length + center.length;
|
||||
if (newInsertionPoint > 3) {
|
||||
// Nudge the cursor to the right to reflect the hyphen
|
||||
// if necessary.
|
||||
newInsertionPoint++;
|
||||
}
|
||||
UITextPosition *newPosition =
|
||||
[textField positionFromPosition:textField.beginningOfDocument offset:(NSInteger)newInsertionPoint];
|
||||
textField.selectedTextRange = [textField textRangeFromPosition:newPosition toPosition:newPosition];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField
|
||||
{
|
||||
[self submitVerificationCode];
|
||||
[textField resignFirstResponder];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setVerificationCodeAndTryToVerify:(NSString *)verificationCode
|
||||
{
|
||||
NSString *rawNewText = verificationCode.digitsOnly;
|
||||
NSString *formattedNewText
|
||||
= (rawNewText.length <= 3 ? rawNewText
|
||||
: [[[rawNewText substringToIndex:3] stringByAppendingString:@"-"]
|
||||
stringByAppendingString:[rawNewText substringFromIndex:3]]);
|
||||
self.challengeTextField.text = formattedNewText;
|
||||
// Move the cursor after the newly inserted text.
|
||||
UITextPosition *newPosition = [self.challengeTextField endOfDocument];
|
||||
self.challengeTextField.selectedTextRange =
|
||||
[self.challengeTextField textRangeFromPosition:newPosition toPosition:newPosition];
|
||||
[self submitVerificationCode];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
{
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
#pragma mark - Orientation
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,13 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RegistrationViewController : OWSViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,646 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RegistrationViewController.h"
|
||||
#import "CodeVerificationViewController.h"
|
||||
#import "CountryCodeViewController.h"
|
||||
#import "PhoneNumber.h"
|
||||
#import "PhoneNumberUtil.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import "TSAccountManager.h"
|
||||
#import "UIView+OWS.h"
|
||||
#import "ViewControllerUtils.h"
|
||||
#import <SignalMessaging/Environment.h>
|
||||
#import <SignalMessaging/OWSNavigationController.h>
|
||||
#import <SignalServiceKit/NSString+SSK.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
#import <SignalMessaging/UIUtil.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
NSString *const kKeychainService_LastRegistered = @"kKeychainService_LastRegistered";
|
||||
NSString *const kKeychainKey_LastRegisteredCountryCode = @"kKeychainKey_LastRegisteredCountryCode";
|
||||
NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegisteredPhoneNumber";
|
||||
|
||||
#endif
|
||||
|
||||
@interface RegistrationViewController () <CountryCodeViewControllerDelegate, UITextFieldDelegate>
|
||||
|
||||
@property (nonatomic) NSString *countryCode;
|
||||
@property (nonatomic) NSString *callingCode;
|
||||
|
||||
@property (nonatomic) UILabel *countryCodeLabel;
|
||||
@property (nonatomic) UITextField *phoneNumberTextField;
|
||||
@property (nonatomic) UILabel *examplePhoneNumberLabel;
|
||||
@property (nonatomic) OWSFlatButton *activateButton;
|
||||
@property (nonatomic) UIActivityIndicatorView *spinnerView;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation RegistrationViewController
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (TSAccountManager *)tsAccountManager
|
||||
{
|
||||
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
|
||||
|
||||
return SSKEnvironment.shared.tsAccountManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
|
||||
self.shouldUseTheme = NO;
|
||||
|
||||
[self createViews];
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
[self populateDefaultCountryNameAndCode];
|
||||
OWSAssertDebug([self.navigationController isKindOfClass:[OWSNavigationController class]]);
|
||||
[SignalApp.sharedApp setSignUpFlowNavigationController:(OWSNavigationController *)self.navigationController];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationBegan]);
|
||||
}
|
||||
|
||||
- (void)createViews
|
||||
{
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
self.view.userInteractionEnabled = YES;
|
||||
[self.view
|
||||
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backgroundTapped:)]];
|
||||
self.view.accessibilityIdentifier = SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, @"root_view");
|
||||
|
||||
UIView *headerWrapper = [UIView containerView];
|
||||
[self.view addSubview:headerWrapper];
|
||||
headerWrapper.backgroundColor = UIColor.ows_signalBrandBlueColor;
|
||||
[headerWrapper autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeBottom];
|
||||
|
||||
UILabel *headerLabel = [UILabel new];
|
||||
headerLabel.text = NSLocalizedString(@"REGISTRATION_TITLE_LABEL", @"");
|
||||
headerLabel.textColor = [UIColor whiteColor];
|
||||
headerLabel.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(20.f, 24.f)];
|
||||
|
||||
NSString *legalTopMatterFormat = NSLocalizedString(@"REGISTRATION_LEGAL_TOP_MATTER_FORMAT",
|
||||
@"legal disclaimer, embeds a tappable {{link title}} which is styled as a hyperlink");
|
||||
NSString *legalTopMatterLinkWord = NSLocalizedString(
|
||||
@"REGISTRATION_LEGAL_TOP_MATTER_LINK_TITLE", @"embedded in legal topmatter, styled as a link");
|
||||
NSString *legalTopMatter = [NSString stringWithFormat:legalTopMatterFormat, legalTopMatterLinkWord];
|
||||
NSMutableAttributedString *attributedLegalTopMatter =
|
||||
[[NSMutableAttributedString alloc] initWithString:legalTopMatter];
|
||||
NSRange linkRange = [legalTopMatter rangeOfString:legalTopMatterLinkWord];
|
||||
NSDictionary *linkStyleAttributes = @{
|
||||
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid),
|
||||
};
|
||||
[attributedLegalTopMatter setAttributes:linkStyleAttributes range:linkRange];
|
||||
|
||||
UILabel *legalTopMatterLabel = [UILabel new];
|
||||
legalTopMatterLabel.textColor = UIColor.whiteColor;
|
||||
legalTopMatterLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(13.f, 15.f)];
|
||||
legalTopMatterLabel.numberOfLines = 0;
|
||||
legalTopMatterLabel.textAlignment = NSTextAlignmentCenter;
|
||||
legalTopMatterLabel.attributedText = attributedLegalTopMatter;
|
||||
legalTopMatterLabel.userInteractionEnabled = YES;
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, legalTopMatterLabel);
|
||||
|
||||
UITapGestureRecognizer *tapGesture =
|
||||
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapLegalTerms:)];
|
||||
[legalTopMatterLabel addGestureRecognizer:tapGesture];
|
||||
|
||||
UIStackView *headerContent = [[UIStackView alloc] initWithArrangedSubviews:@[ headerLabel ]];
|
||||
[headerContent addArrangedSubview:legalTopMatterLabel];
|
||||
headerContent.axis = UILayoutConstraintAxisVertical;
|
||||
headerContent.alignment = UIStackViewAlignmentCenter;
|
||||
headerContent.spacing = ScaleFromIPhone5To7Plus(8, 16);
|
||||
headerContent.layoutMarginsRelativeArrangement = YES;
|
||||
|
||||
{
|
||||
CGFloat topMargin = ScaleFromIPhone5To7Plus(4, 16);
|
||||
CGFloat bottomMargin = ScaleFromIPhone5To7Plus(8, 16);
|
||||
headerContent.layoutMargins = UIEdgeInsetsMake(topMargin, 40, bottomMargin, 40);
|
||||
}
|
||||
|
||||
[headerWrapper addSubview:headerContent];
|
||||
[headerContent autoPinToTopLayoutGuideOfViewController:self withInset:0];
|
||||
[headerContent autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeTop];
|
||||
|
||||
const CGFloat kRowHeight = 60.f;
|
||||
const CGFloat kRowHMargin = 20.f;
|
||||
const CGFloat kSeparatorHeight = 1.f;
|
||||
const CGFloat kExamplePhoneNumberVSpacing = 8.f;
|
||||
const CGFloat fontSizePoints = ScaleFromIPhone5To7Plus(16.f, 20.f);
|
||||
|
||||
UIView *contentView = [UIView containerView];
|
||||
[contentView setHLayoutMargins:kRowHMargin];
|
||||
contentView.backgroundColor = [UIColor whiteColor];
|
||||
[self.view addSubview:contentView];
|
||||
[contentView autoPinToBottomLayoutGuideOfViewController:self withInset:0];
|
||||
[contentView autoPinWidthToSuperview];
|
||||
[contentView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:headerContent];
|
||||
|
||||
// Country
|
||||
UIView *countryRow = [UIView containerView];
|
||||
[contentView addSubview:countryRow];
|
||||
[countryRow autoPinLeadingAndTrailingToSuperviewMargin];
|
||||
[countryRow autoPinEdgeToSuperviewEdge:ALEdgeTop];
|
||||
[countryRow autoSetDimension:ALDimensionHeight toSize:kRowHeight];
|
||||
[countryRow
|
||||
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(countryCodeRowWasTapped:)]];
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, countryRow);
|
||||
|
||||
UILabel *countryNameLabel = [UILabel new];
|
||||
countryNameLabel.text
|
||||
= NSLocalizedString(@"REGISTRATION_DEFAULT_COUNTRY_NAME", @"Label for the country code field");
|
||||
countryNameLabel.textColor = [UIColor blackColor];
|
||||
countryNameLabel.font = [UIFont ows_mediumFontWithSize:fontSizePoints];
|
||||
[countryRow addSubview:countryNameLabel];
|
||||
[countryNameLabel autoVCenterInSuperview];
|
||||
[countryNameLabel autoPinLeadingToSuperviewMargin];
|
||||
|
||||
UILabel *countryCodeLabel = [UILabel new];
|
||||
self.countryCodeLabel = countryCodeLabel;
|
||||
countryCodeLabel.textColor = [UIColor ows_materialBlueColor];
|
||||
countryCodeLabel.font = [UIFont ows_mediumFontWithSize:fontSizePoints + 2.f];
|
||||
[countryRow addSubview:countryCodeLabel];
|
||||
[countryCodeLabel autoVCenterInSuperview];
|
||||
[countryCodeLabel autoPinTrailingToSuperviewMargin];
|
||||
|
||||
UIView *separatorView1 = [UIView new];
|
||||
separatorView1.backgroundColor = [UIColor colorWithWhite:0.75f alpha:1.f];
|
||||
[contentView addSubview:separatorView1];
|
||||
[separatorView1 autoPinWidthToSuperview];
|
||||
[separatorView1 autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:countryRow];
|
||||
[separatorView1 autoSetDimension:ALDimensionHeight toSize:kSeparatorHeight];
|
||||
|
||||
// Phone Number
|
||||
UIView *phoneNumberRow = [UIView containerView];
|
||||
[contentView addSubview:phoneNumberRow];
|
||||
[phoneNumberRow autoPinLeadingAndTrailingToSuperviewMargin];
|
||||
[phoneNumberRow autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:separatorView1];
|
||||
[phoneNumberRow autoSetDimension:ALDimensionHeight toSize:kRowHeight];
|
||||
|
||||
UILabel *phoneNumberLabel = [UILabel new];
|
||||
phoneNumberLabel.text
|
||||
= NSLocalizedString(@"REGISTRATION_PHONENUMBER_BUTTON", @"Label for the phone number textfield");
|
||||
phoneNumberLabel.textColor = [UIColor blackColor];
|
||||
phoneNumberLabel.font = [UIFont ows_mediumFontWithSize:fontSizePoints];
|
||||
[phoneNumberRow addSubview:phoneNumberLabel];
|
||||
[phoneNumberLabel autoVCenterInSuperview];
|
||||
[phoneNumberLabel autoPinLeadingToSuperviewMargin];
|
||||
|
||||
UITextField *phoneNumberTextField;
|
||||
if (UIDevice.currentDevice.isShorterThanIPhone5) {
|
||||
phoneNumberTextField = [DismissableTextField new];
|
||||
} else {
|
||||
phoneNumberTextField = [OWSTextField new];
|
||||
}
|
||||
|
||||
phoneNumberTextField.textAlignment = NSTextAlignmentRight;
|
||||
phoneNumberTextField.delegate = self;
|
||||
phoneNumberTextField.keyboardType = UIKeyboardTypeNumberPad;
|
||||
phoneNumberTextField.placeholder = NSLocalizedString(
|
||||
@"REGISTRATION_ENTERNUMBER_DEFAULT_TEXT", @"Placeholder text for the phone number textfield");
|
||||
self.phoneNumberTextField = phoneNumberTextField;
|
||||
phoneNumberTextField.textColor = [UIColor ows_materialBlueColor];
|
||||
phoneNumberTextField.font = [UIFont ows_mediumFontWithSize:fontSizePoints + 2];
|
||||
[phoneNumberRow addSubview:phoneNumberTextField];
|
||||
[phoneNumberTextField autoVCenterInSuperview];
|
||||
[phoneNumberTextField autoPinTrailingToSuperviewMargin];
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, phoneNumberTextField);
|
||||
|
||||
UILabel *examplePhoneNumberLabel = [UILabel new];
|
||||
self.examplePhoneNumberLabel = examplePhoneNumberLabel;
|
||||
examplePhoneNumberLabel.font = [UIFont ows_regularFontWithSize:fontSizePoints - 2.f];
|
||||
examplePhoneNumberLabel.textColor = Theme.middleGrayColor;
|
||||
[contentView addSubview:examplePhoneNumberLabel];
|
||||
[examplePhoneNumberLabel autoPinTrailingToSuperviewMargin];
|
||||
[examplePhoneNumberLabel autoPinEdge:ALEdgeTop
|
||||
toEdge:ALEdgeBottom
|
||||
ofView:phoneNumberTextField
|
||||
withOffset:kExamplePhoneNumberVSpacing];
|
||||
|
||||
UIView *separatorView2 = [UIView new];
|
||||
separatorView2.backgroundColor = [UIColor colorWithWhite:0.75f alpha:1.f];
|
||||
[contentView addSubview:separatorView2];
|
||||
[separatorView2 autoPinWidthToSuperview];
|
||||
[separatorView2 autoPinEdge:ALEdgeTop
|
||||
toEdge:ALEdgeBottom
|
||||
ofView:phoneNumberRow
|
||||
withOffset:examplePhoneNumberLabel.font.lineHeight];
|
||||
[separatorView2 autoSetDimension:ALDimensionHeight toSize:kSeparatorHeight];
|
||||
|
||||
// Activate Button
|
||||
const CGFloat kActivateButtonHeight = 47.f;
|
||||
// NOTE: We use ows_signalBrandBlueColor instead of ows_materialBlueColor
|
||||
// throughout the onboarding flow to be consistent with the headers.
|
||||
OWSFlatButton *activateButton = [OWSFlatButton buttonWithTitle:NSLocalizedString(@"REGISTRATION_VERIFY_DEVICE", @"")
|
||||
font:[OWSFlatButton fontForHeight:kActivateButtonHeight]
|
||||
titleColor:[UIColor whiteColor]
|
||||
backgroundColor:[UIColor ows_signalBrandBlueColor]
|
||||
target:self
|
||||
selector:@selector(didTapRegisterButton)];
|
||||
self.activateButton = activateButton;
|
||||
[contentView addSubview:activateButton];
|
||||
[activateButton autoPinLeadingAndTrailingToSuperviewMargin];
|
||||
[activateButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:separatorView2 withOffset:15];
|
||||
[activateButton autoSetDimension:ALDimensionHeight toSize:kActivateButtonHeight];
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, activateButton);
|
||||
|
||||
UIActivityIndicatorView *spinnerView =
|
||||
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
|
||||
self.spinnerView = spinnerView;
|
||||
[activateButton addSubview:spinnerView];
|
||||
[spinnerView autoVCenterInSuperview];
|
||||
[spinnerView autoSetDimension:ALDimensionWidth toSize:20.f];
|
||||
[spinnerView autoSetDimension:ALDimensionHeight toSize:20.f];
|
||||
[spinnerView autoPinTrailingToSuperviewMarginWithInset:20.f];
|
||||
[spinnerView stopAnimating];
|
||||
|
||||
NSString *bottomTermsLinkText = NSLocalizedString(@"REGISTRATION_LEGAL_TERMS_LINK",
|
||||
@"one line label below submit button on registration screen, which links to an external webpage.");
|
||||
UIButton *bottomLegalLinkButton = [UIButton new];
|
||||
bottomLegalLinkButton.titleLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(13.f, 15.f)];
|
||||
[bottomLegalLinkButton setTitleColor:UIColor.ows_materialBlueColor forState:UIControlStateNormal];
|
||||
[bottomLegalLinkButton setTitle:bottomTermsLinkText forState:UIControlStateNormal];
|
||||
[contentView addSubview:bottomLegalLinkButton];
|
||||
[bottomLegalLinkButton addTarget:self
|
||||
action:@selector(didTapLegalTerms:)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[bottomLegalLinkButton autoPinLeadingAndTrailingToSuperviewMargin];
|
||||
[bottomLegalLinkButton autoPinEdge:ALEdgeTop
|
||||
toEdge:ALEdgeBottom
|
||||
ofView:activateButton
|
||||
withOffset:ScaleFromIPhone5To7Plus(8, 12)];
|
||||
[bottomLegalLinkButton setCompressionResistanceHigh];
|
||||
[bottomLegalLinkButton setContentHuggingHigh];
|
||||
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, bottomLegalLinkButton);
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[self.activateButton setEnabled:YES];
|
||||
[self.spinnerView stopAnimating];
|
||||
[self.phoneNumberTextField becomeFirstResponder];
|
||||
|
||||
if (self.tsAccountManager.isReregistering) {
|
||||
// If re-registering, pre-populate the country (country code, calling code, country name)
|
||||
// and phone number state.
|
||||
NSString *_Nullable phoneNumberE164 = self.tsAccountManager.reregisterationPhoneNumber;
|
||||
if (!phoneNumberE164) {
|
||||
OWSFailDebug(@"Could not resume re-registration; missing phone number.");
|
||||
} else if ([self tryToApplyPhoneNumberE164:phoneNumberE164]) {
|
||||
// Don't let user edit their phone number while re-registering.
|
||||
self.phoneNumberTextField.enabled = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)tryToApplyPhoneNumberE164:(NSString *)phoneNumberE164
|
||||
{
|
||||
OWSAssertDebug(phoneNumberE164);
|
||||
|
||||
if (phoneNumberE164.length < 1) {
|
||||
OWSFailDebug(@"Could not resume re-registration; invalid phoneNumberE164.");
|
||||
return NO;
|
||||
}
|
||||
PhoneNumber *_Nullable parsedPhoneNumber = [PhoneNumber phoneNumberFromE164:phoneNumberE164];
|
||||
if (!parsedPhoneNumber) {
|
||||
OWSFailDebug(@"Could not resume re-registration; couldn't parse phoneNumberE164.");
|
||||
return NO;
|
||||
}
|
||||
NSNumber *_Nullable callingCode = parsedPhoneNumber.getCountryCode;
|
||||
if (!callingCode) {
|
||||
OWSFailDebug(@"Could not resume re-registration; missing callingCode.");
|
||||
return NO;
|
||||
}
|
||||
NSString *callingCodeText = [NSString stringWithFormat:@"+%d", callingCode.intValue];
|
||||
NSArray<NSString *> *_Nullable countryCodes =
|
||||
[PhoneNumberUtil.sharedThreadLocal countryCodesFromCallingCode:callingCodeText];
|
||||
if (countryCodes.count < 1) {
|
||||
OWSFailDebug(@"Could not resume re-registration; unknown countryCode.");
|
||||
return NO;
|
||||
}
|
||||
NSString *countryCode = countryCodes.firstObject;
|
||||
NSString *_Nullable countryName = [PhoneNumberUtil countryNameFromCountryCode:countryCode];
|
||||
if (!countryName) {
|
||||
OWSFailDebug(@"Could not resume re-registration; unknown countryName.");
|
||||
return NO;
|
||||
}
|
||||
if (![phoneNumberE164 hasPrefix:callingCodeText]) {
|
||||
OWSFailDebug(@"Could not resume re-registration; non-matching calling code.");
|
||||
return NO;
|
||||
}
|
||||
NSString *phoneNumberWithoutCallingCode = [phoneNumberE164 substringFromIndex:callingCodeText.length];
|
||||
|
||||
[self updateCountryWithName:countryName callingCode:callingCodeText countryCode:countryCode];
|
||||
self.phoneNumberTextField.text = phoneNumberWithoutCallingCode;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Country
|
||||
|
||||
- (void)populateDefaultCountryNameAndCode
|
||||
{
|
||||
NSString *countryCode = [PhoneNumber defaultCountryCode];
|
||||
|
||||
#ifdef DEBUG
|
||||
if ([self lastRegisteredCountryCode].length > 0) {
|
||||
countryCode = [self lastRegisteredCountryCode];
|
||||
}
|
||||
self.phoneNumberTextField.text = [self lastRegisteredPhoneNumber];
|
||||
#endif
|
||||
|
||||
NSNumber *callingCode = [[PhoneNumberUtil sharedThreadLocal].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
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssertDebug(countryName.length > 0);
|
||||
OWSAssertDebug(callingCode.length > 0);
|
||||
OWSAssertDebug(countryCode.length > 0);
|
||||
|
||||
_countryCode = countryCode;
|
||||
_callingCode = callingCode;
|
||||
|
||||
NSString *title = [NSString stringWithFormat:@"%@ (%@)", callingCode, countryCode.localizedUppercaseString];
|
||||
self.countryCodeLabel.text = title;
|
||||
[self.countryCodeLabel setNeedsLayout];
|
||||
|
||||
self.examplePhoneNumberLabel.text =
|
||||
[ViewControllerUtils examplePhoneNumberForCountryCode:countryCode callingCode:callingCode];
|
||||
[self.examplePhoneNumberLabel setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)didTapRegisterButton
|
||||
{
|
||||
NSString *phoneNumberText = [_phoneNumberTextField.text ows_stripped];
|
||||
if (phoneNumberText.length < 1) {
|
||||
[OWSAlerts
|
||||
showAlertWithTitle:NSLocalizedString(@"REGISTRATION_VIEW_NO_PHONE_NUMBER_ALERT_TITLE",
|
||||
@"Title of alert indicating that users needs to enter a phone number to register.")
|
||||
message:
|
||||
NSLocalizedString(@"REGISTRATION_VIEW_NO_PHONE_NUMBER_ALERT_MESSAGE",
|
||||
@"Message of alert indicating that users needs to enter a phone number to register.")];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *countryCode = self.countryCode;
|
||||
NSString *phoneNumber = [NSString stringWithFormat:@"%@%@", _callingCode, phoneNumberText];
|
||||
PhoneNumber *localNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber];
|
||||
NSString *parsedPhoneNumber = localNumber.toE164;
|
||||
if (parsedPhoneNumber.length < 1
|
||||
|| ![[PhoneNumberValidator new] isValidForRegistrationWithPhoneNumber:localNumber]) {
|
||||
[OWSAlerts showAlertWithTitle:
|
||||
NSLocalizedString(@"REGISTRATION_VIEW_INVALID_PHONE_NUMBER_ALERT_TITLE",
|
||||
@"Title of alert indicating that users needs to enter a valid phone number to register.")
|
||||
message:NSLocalizedString(@"REGISTRATION_VIEW_INVALID_PHONE_NUMBER_ALERT_MESSAGE",
|
||||
@"Message of alert indicating that users needs to enter a valid phone number "
|
||||
@"to register.")];
|
||||
return;
|
||||
}
|
||||
|
||||
if (UIDevice.currentDevice.isIPad) {
|
||||
[OWSAlerts showConfirmationAlertWithTitle:NSLocalizedString(@"REGISTRATION_IPAD_CONFIRM_TITLE",
|
||||
@"alert title when registering an iPad")
|
||||
message:NSLocalizedString(@"REGISTRATION_IPAD_CONFIRM_BODY",
|
||||
@"alert body when registering an iPad")
|
||||
proceedTitle:NSLocalizedString(@"REGISTRATION_IPAD_CONFIRM_BUTTON",
|
||||
@"button text to proceed with registration when on an iPad")
|
||||
proceedAction:^(UIAlertAction *_Nonnull action) {
|
||||
[self sendCodeActionWithParsedPhoneNumber:parsedPhoneNumber
|
||||
phoneNumberText:phoneNumberText
|
||||
countryCode:countryCode];
|
||||
}];
|
||||
} else {
|
||||
[self sendCodeActionWithParsedPhoneNumber:parsedPhoneNumber
|
||||
phoneNumberText:phoneNumberText
|
||||
countryCode:countryCode];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendCodeActionWithParsedPhoneNumber:(NSString *)parsedPhoneNumber
|
||||
phoneNumberText:(NSString *)phoneNumberText
|
||||
countryCode:(NSString *)countryCode
|
||||
{
|
||||
[self.activateButton setEnabled:NO];
|
||||
[self.spinnerView startAnimating];
|
||||
[self.phoneNumberTextField resignFirstResponder];
|
||||
|
||||
__weak RegistrationViewController *weakSelf = self;
|
||||
[self.tsAccountManager registerWithPhoneNumber:parsedPhoneNumber
|
||||
captchaToken:nil
|
||||
success:^{
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteredPhoneNumber]);
|
||||
|
||||
[weakSelf.spinnerView stopAnimating];
|
||||
|
||||
CodeVerificationViewController *vc = [CodeVerificationViewController new];
|
||||
[weakSelf.navigationController pushViewController:vc animated:YES];
|
||||
|
||||
#ifdef DEBUG
|
||||
[weakSelf setLastRegisteredCountryCode:countryCode];
|
||||
[weakSelf setLastRegisteredPhoneNumber:phoneNumberText];
|
||||
#endif
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
if (error.code == 400) {
|
||||
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"REGISTRATION_ERROR", nil)
|
||||
message:NSLocalizedString(@"REGISTRATION_NON_VALID_NUMBER", nil)];
|
||||
} else {
|
||||
[OWSAlerts showAlertWithTitle:error.localizedDescription message:error.localizedRecoverySuggestion];
|
||||
}
|
||||
|
||||
[weakSelf.activateButton setEnabled:YES];
|
||||
[weakSelf.spinnerView stopAnimating];
|
||||
[weakSelf.phoneNumberTextField becomeFirstResponder];
|
||||
}
|
||||
smsVerification:YES];
|
||||
}
|
||||
|
||||
- (void)countryCodeRowWasTapped:(UIGestureRecognizer *)sender
|
||||
{
|
||||
if (self.tsAccountManager.isReregistering) {
|
||||
// Don't let user edit their phone number while re-registering.
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
||||
[self changeCountryCodeTapped];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didTapLegalTerms:(UIButton *)sender
|
||||
{
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:kLegalTermsUrlString]];
|
||||
}
|
||||
|
||||
- (void)changeCountryCodeTapped
|
||||
{
|
||||
CountryCodeViewController *countryCodeController = [CountryCodeViewController new];
|
||||
countryCodeController.countryCodeDelegate = self;
|
||||
countryCodeController.interfaceOrientationMask = UIInterfaceOrientationMaskPortrait;
|
||||
OWSNavigationController *navigationController =
|
||||
[[OWSNavigationController alloc] initWithRootViewController:countryCodeController];
|
||||
[self presentViewController:navigationController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)backgroundTapped:(UIGestureRecognizer *)sender
|
||||
{
|
||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
||||
[self.phoneNumberTextField becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - CountryCodeViewControllerDelegate
|
||||
|
||||
- (void)countryCodeViewController:(CountryCodeViewController *)vc
|
||||
didSelectCountryCode:(NSString *)countryCode
|
||||
countryName:(NSString *)countryName
|
||||
callingCode:(NSString *)callingCode
|
||||
{
|
||||
OWSAssertDebug(countryCode.length > 0);
|
||||
OWSAssertDebug(countryName.length > 0);
|
||||
OWSAssertDebug(callingCode.length > 0);
|
||||
|
||||
[self updateCountryWithName:countryName callingCode:callingCode countryCode:countryCode];
|
||||
|
||||
// Trigger the formatting logic with a no-op edit.
|
||||
[self textField:self.phoneNumberTextField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard notifications
|
||||
|
||||
- (void)initializeKeyboardHandlers
|
||||
{
|
||||
UITapGestureRecognizer *outsideTabRecognizer =
|
||||
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboardFromAppropriateSubView)];
|
||||
[self.view addGestureRecognizer:outsideTabRecognizer];
|
||||
}
|
||||
|
||||
- (void)dismissKeyboardFromAppropriateSubView
|
||||
{
|
||||
[self.view endEditing:NO];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textField:(UITextField *)textField
|
||||
shouldChangeCharactersInRange:(NSRange)range
|
||||
replacementString:(NSString *)insertionText
|
||||
{
|
||||
|
||||
[ViewControllerUtils phoneNumberTextField:textField
|
||||
shouldChangeCharactersInRange:range
|
||||
replacementString:insertionText
|
||||
callingCode:_callingCode];
|
||||
|
||||
return NO; // inform our caller that we took care of performing the change
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField
|
||||
{
|
||||
[self didTapRegisterButton];
|
||||
[textField resignFirstResponder];
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Debug
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
- (NSString *_Nullable)debugValueForKey:(NSString *)key
|
||||
{
|
||||
OWSCAssertDebug([NSThread isMainThread]);
|
||||
OWSCAssertDebug(key.length > 0);
|
||||
|
||||
NSError *error;
|
||||
NSString *_Nullable value =
|
||||
[CurrentAppContext().keychainStorage stringForService:kKeychainService_LastRegistered key:key error:&error];
|
||||
if (error || !value) {
|
||||
OWSLogWarn(@"Could not retrieve 'last registered' value from keychain: %@.", error);
|
||||
return nil;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)setDebugValue:(NSString *)value forKey:(NSString *)key
|
||||
{
|
||||
OWSCAssertDebug([NSThread isMainThread]);
|
||||
OWSCAssertDebug(key.length > 0);
|
||||
OWSCAssertDebug(value.length > 0);
|
||||
|
||||
NSError *error;
|
||||
BOOL success = [CurrentAppContext().keychainStorage setString:value
|
||||
service:kKeychainService_LastRegistered
|
||||
key:key
|
||||
error:&error];
|
||||
if (!success || error) {
|
||||
OWSLogError(@"Error persisting 'last registered' value in keychain: %@", error);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *_Nullable)lastRegisteredCountryCode
|
||||
{
|
||||
return [self debugValueForKey:kKeychainKey_LastRegisteredCountryCode];
|
||||
}
|
||||
|
||||
- (void)setLastRegisteredCountryCode:(NSString *)value
|
||||
{
|
||||
[self setDebugValue:value forKey:kKeychainKey_LastRegisteredCountryCode];
|
||||
}
|
||||
|
||||
- (NSString *_Nullable)lastRegisteredPhoneNumber
|
||||
{
|
||||
return [self debugValueForKey:kKeychainKey_LastRegisteredPhoneNumber];
|
||||
}
|
||||
|
||||
- (void)setLastRegisteredPhoneNumber:(NSString *)value
|
||||
{
|
||||
[self setDebugValue:value forKey:kKeychainKey_LastRegisteredPhoneNumber];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
{
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
#pragma mark - Orientation
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
Loading…
Reference in New Issue