Merge branch 'mkirk/2fa-reminders'
This commit is contained in:
commit
d516bcc29b
@ -43,7 +43,7 @@
|
||||
344F248420069E9C00CFB4F4 /* CountryCodeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 344F248220069E9B00CFB4F4 /* CountryCodeViewController.h */; };
|
||||
344F248520069E9C00CFB4F4 /* CountryCodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 344F248320069E9B00CFB4F4 /* CountryCodeViewController.m */; };
|
||||
344F248720069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248620069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift */; };
|
||||
344F248A20069F0600CFB4F4 /* ViewControllerUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 344F248820069F0600CFB4F4 /* ViewControllerUtils.h */; };
|
||||
344F248A20069F0600CFB4F4 /* ViewControllerUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 344F248820069F0600CFB4F4 /* ViewControllerUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
344F248B20069F0600CFB4F4 /* ViewControllerUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 344F248920069F0600CFB4F4 /* ViewControllerUtils.m */; };
|
||||
344F248D2007CCD600CFB4F4 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */; };
|
||||
344F248F2007D7F200CFB4F4 /* OWSMessagesBubbleImageFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248E2007D7F200CFB4F4 /* OWSMessagesBubbleImageFactory.swift */; };
|
||||
@ -345,6 +345,8 @@
|
||||
45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */; };
|
||||
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */; };
|
||||
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D231761DC7E8F10034FA89 /* SessionResetJob.swift */; };
|
||||
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */; };
|
||||
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */ = {isa = PBXBuildFile; fileRef = 45D308AC2049A439000189E4 /* PinEntryView.m */; };
|
||||
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */; };
|
||||
45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; };
|
||||
45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; };
|
||||
@ -926,6 +928,9 @@
|
||||
45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "Signal/src/util/Launch Screen.storyboard"; sourceTree = SOURCE_ROOT; };
|
||||
45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncPushTokensJob.swift; sourceTree = "<group>"; };
|
||||
45D231761DC7E8F10034FA89 /* SessionResetJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionResetJob.swift; sourceTree = "<group>"; };
|
||||
45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS2FAReminderViewController.swift; sourceTree = "<group>"; };
|
||||
45D308AB2049A439000189E4 /* PinEntryView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PinEntryView.h; sourceTree = "<group>"; };
|
||||
45D308AC2049A439000189E4 /* PinEntryView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PinEntryView.m; sourceTree = "<group>"; };
|
||||
45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompareSafetyNumbersActivity.swift; sourceTree = "<group>"; };
|
||||
45E282DE1D08E67800ADD4C8 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = translations/gl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
45E282DF1D08E6CC00ADD4C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = translations/id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@ -1514,6 +1519,7 @@
|
||||
340CB2251EAC25820001CAA1 /* UpdateGroupViewController.h */,
|
||||
340CB2261EAC25820001CAA1 /* UpdateGroupViewController.m */,
|
||||
34D1F0BE1F8EC1760066283D /* Utils */,
|
||||
45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */,
|
||||
);
|
||||
path = ViewControllers;
|
||||
sourceTree = "<group>";
|
||||
@ -1919,6 +1925,8 @@
|
||||
450D19111F85236600970622 /* RemoteVideoView.h */,
|
||||
450D19121F85236600970622 /* RemoteVideoView.m */,
|
||||
4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */,
|
||||
45D308AB2049A439000189E4 /* PinEntryView.h */,
|
||||
45D308AC2049A439000189E4 /* PinEntryView.m */,
|
||||
);
|
||||
name = Views;
|
||||
path = views;
|
||||
@ -3086,6 +3094,7 @@
|
||||
452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */,
|
||||
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
|
||||
34CA1C271F7156F300E51C51 /* MessageDetailViewController.swift in Sources */,
|
||||
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */,
|
||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
|
||||
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
|
||||
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
|
||||
@ -3101,6 +3110,7 @@
|
||||
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */,
|
||||
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */,
|
||||
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */,
|
||||
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */,
|
||||
45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */,
|
||||
45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */,
|
||||
34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */,
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#import "DebugLogger.h"
|
||||
#import "MainAppContext.h"
|
||||
#import "NotificationsManager.h"
|
||||
#import "OWS2FASettingsViewController.h"
|
||||
#import "OWSBackup.h"
|
||||
#import "OWSNavigationController.h"
|
||||
#import "Pastelog.h"
|
||||
@ -31,6 +32,7 @@
|
||||
#import <SignalMessaging/VersionMigrations.h>
|
||||
#import <SignalServiceKit/AppReadiness.h>
|
||||
#import <SignalServiceKit/NSUserDefaults+OWS.h>
|
||||
#import <SignalServiceKit/OWS2FAManager.h>
|
||||
#import <SignalServiceKit/OWSBatchMessageProcessor.h>
|
||||
#import <SignalServiceKit/OWSDisappearingMessagesJob.h>
|
||||
#import <SignalServiceKit/OWSFailedAttachmentDownloadsJob.h>
|
||||
@ -660,6 +662,19 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
|
||||
[OWSSyncPushTokensJob runWithAccountManager:SignalApp.sharedApp.accountManager
|
||||
preferences:[Environment preferences]];
|
||||
}
|
||||
|
||||
if ([OWS2FAManager sharedManager].isDueForReminder) {
|
||||
if (!self.hasInitialRootViewController || self.window.rootViewController == nil) {
|
||||
DDLogDebug(
|
||||
@"%@ Skipping 2FA reminder since there isn't yet an initial view controller", self.logTag);
|
||||
} else {
|
||||
UIViewController *rootViewController = self.window.rootViewController;
|
||||
UINavigationController *reminderNavController =
|
||||
[OWS2FAReminderViewController wrappedInNavController];
|
||||
|
||||
[rootViewController presentViewController:reminderNavController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
#import "FingerprintViewController.h"
|
||||
#import "HomeViewController.h"
|
||||
#import "MediaDetailViewController.h"
|
||||
#import "NSString+OWS.h"
|
||||
#import "NotificationSettingsViewController.h"
|
||||
#import "NotificationsManager.h"
|
||||
#import "OWSAnyTouchGestureRecognizer.h"
|
||||
@ -25,6 +24,7 @@
|
||||
#import "OWSNavigationController.h"
|
||||
#import "OWSProgressView.h"
|
||||
#import "OWSWebRTCDataProtos.pb.h"
|
||||
#import "PinEntryView.h"
|
||||
#import "PrivacySettingsTableViewController.h"
|
||||
#import "ProfileViewController.h"
|
||||
#import "PushManager.h"
|
||||
|
||||
@ -103,7 +103,7 @@ class DebugUIFileBrowser: OWSTableViewController {
|
||||
return attributes.map { (fileAttribute: FileAttributeKey, value: Any) in
|
||||
let title = fileAttribute.rawValue.replacingOccurrences(of: "NSFile", with: "")
|
||||
return OWSTableItem(title: "\(title): \(value)") {
|
||||
OWSAlerts.showAlert(withTitle: title, message: "\(value)")
|
||||
OWSAlerts.showAlert(title: title, message: "\(value)")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@ -132,7 +132,7 @@ class DebugUIFileBrowser: OWSTableViewController {
|
||||
}
|
||||
|
||||
guard let inputString = textField.text, inputString.count >= 4 else {
|
||||
OWSAlerts.showAlert(withTitle: "new file name missing or less than 4 chars")
|
||||
OWSAlerts.showAlert(title: "new file name missing or less than 4 chars")
|
||||
return
|
||||
}
|
||||
|
||||
@ -177,7 +177,7 @@ class DebugUIFileBrowser: OWSTableViewController {
|
||||
}
|
||||
|
||||
guard let inputString = textField.text, inputString.count >= 4 else {
|
||||
OWSAlerts.showAlert(withTitle: "new file dir missing or less than 4 chars")
|
||||
OWSAlerts.showAlert(title: "new file dir missing or less than 4 chars")
|
||||
return
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ class DebugUIFileBrowser: OWSTableViewController {
|
||||
return
|
||||
}
|
||||
|
||||
OWSAlerts.showConfirmationAlert(withTitle: "Delete \(strongSelf.fileURL.path)?") { _ in
|
||||
OWSAlerts.showConfirmationAlert(title: "Delete \(strongSelf.fileURL.path)?") { _ in
|
||||
Logger.debug("\(strongSelf.logTag) deleting file at \(strongSelf.fileURL.path)")
|
||||
do {
|
||||
try strongSelf.fileManager.removeItem(atPath: strongSelf.fileURL.path)
|
||||
@ -295,7 +295,7 @@ class DebugUIFileBrowser: OWSTableViewController {
|
||||
}
|
||||
|
||||
guard let inputString = textField.text, inputString.count >= 4 else {
|
||||
OWSAlerts.showAlert(withTitle: "file name missing or less than 4 chars")
|
||||
OWSAlerts.showAlert(title: "file name missing or less than 4 chars")
|
||||
return
|
||||
}
|
||||
|
||||
@ -333,7 +333,7 @@ class DebugUIFileBrowser: OWSTableViewController {
|
||||
}
|
||||
|
||||
guard let inputString = textField.text, inputString.count >= 4 else {
|
||||
OWSAlerts.showAlert(withTitle: "dir name missing or less than 4 chars")
|
||||
OWSAlerts.showAlert(title: "dir name missing or less than 4 chars")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -106,6 +106,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
animated:YES];
|
||||
}]];
|
||||
|
||||
[items addObject:[OWSTableItem itemWithTitle:@"Show 2FA Reminder"
|
||||
actionBlock:^() {
|
||||
UINavigationController *navController =
|
||||
[OWS2FAReminderViewController wrappedInNavController];
|
||||
[[[UIApplication sharedApplication] frontmostViewController]
|
||||
presentViewController:navController
|
||||
animated:YES
|
||||
completion:nil];
|
||||
}]];
|
||||
|
||||
#ifdef DEBUG
|
||||
[items addObject:[OWSTableItem subPageItemWithText:@"Share UIImage"
|
||||
actionBlock:^(UIViewController *viewController) {
|
||||
|
||||
@ -454,7 +454,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
|
||||
progressiveSearchTimer = nil
|
||||
|
||||
guard let text = searchBar.text else {
|
||||
OWSAlerts.showAlert(withTitle: NSLocalizedString("ALERT_ERROR_TITLE",
|
||||
OWSAlerts.showAlert(title: NSLocalizedString("ALERT_ERROR_TITLE",
|
||||
comment: ""),
|
||||
message: NSLocalizedString("GIF_PICKER_VIEW_MISSING_QUERY",
|
||||
comment: "Alert message shown when user tries to search for GIFs without entering any search terms."))
|
||||
|
||||
@ -3,24 +3,20 @@
|
||||
//
|
||||
|
||||
#import "OWS2FARegistrationViewController.h"
|
||||
#import "PinEntryView.h"
|
||||
#import "ProfileViewController.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <PromiseKit/AnyPromise.h>
|
||||
#import <SignalMessaging/UIColor+OWS.h>
|
||||
#import <SignalMessaging/UIFont+OWS.h>
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SignalMessaging/UIViewController+OWS.h>
|
||||
#import <SignalServiceKit/OWS2FAManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWS2FARegistrationViewController () <UITextFieldDelegate>
|
||||
@interface OWS2FARegistrationViewController () <PinEntryViewDelegate>
|
||||
|
||||
@property (nonatomic, readonly) AccountManager *accountManager;
|
||||
|
||||
@property (nonatomic) UITextField *pinTextfield;
|
||||
|
||||
@property (nonatomic) OWSFlatButton *submitButton;
|
||||
@property (nonatomic) PinEntryView *entryView;
|
||||
|
||||
@end
|
||||
|
||||
@ -60,171 +56,54 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
self.view.backgroundColor = UIColor.whiteColor;
|
||||
|
||||
[self createContents];
|
||||
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 autoPinEdgesToSuperviewMargins];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self updateEnabling];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
// If we're using a PIN textfield, select it.
|
||||
[self.pinTextfield becomeFirstResponder];
|
||||
[self.entryView makePinTextFieldFirstResponder];
|
||||
}
|
||||
|
||||
- (UILabel *)createLabelWithText:(NSString *)text
|
||||
#pragma mark - PinEntryViewDelegate
|
||||
|
||||
- (void)pinEntryView:(PinEntryView *)entryView submittedPinCode:(NSString *)pinCode
|
||||
{
|
||||
UILabel *label = [UILabel new];
|
||||
label.textColor = [UIColor blackColor];
|
||||
label.text = text;
|
||||
label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
|
||||
label.numberOfLines = 0;
|
||||
label.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
[self.view addSubview:label];
|
||||
return label;
|
||||
OWSAssert(self.entryView.hasValidPin);
|
||||
|
||||
[self tryToRegisterWithPinCode:pinCode];
|
||||
}
|
||||
|
||||
- (void)createPinTextfield
|
||||
- (void)pinEntryViewForgotPinLinkTapped:(PinEntryView *)entryView
|
||||
{
|
||||
self.pinTextfield = [UITextField new];
|
||||
self.pinTextfield.textColor = [UIColor blackColor];
|
||||
self.pinTextfield.placeholder
|
||||
= NSLocalizedString(@"2FA_PIN_DEFAULT_TEXT", @"Text field placeholder when entering a 'two-factor auth pin'.");
|
||||
self.pinTextfield.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(30.f, 36.f)];
|
||||
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
|
||||
self.pinTextfield.keyboardType = UIKeyboardTypeNumberPad;
|
||||
self.pinTextfield.delegate = self;
|
||||
self.pinTextfield.secureTextEntry = YES;
|
||||
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
|
||||
[self.view addSubview:self.pinTextfield];
|
||||
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];
|
||||
}
|
||||
|
||||
- (UILabel *)createForgotLink
|
||||
#pragma mark - Registration
|
||||
|
||||
- (void)tryToRegisterWithPinCode:(NSString *)pinCode
|
||||
{
|
||||
UILabel *label = [UILabel new];
|
||||
label.textColor = [UIColor ows_materialBlueColor];
|
||||
NSString *text = NSLocalizedString(
|
||||
@"REGISTER_2FA_FORGOT_PIN", @"Label for 'I forgot my PIN' link in the 2FA registration view.");
|
||||
label.attributedText = [[NSAttributedString alloc]
|
||||
initWithString:text
|
||||
attributes:@{
|
||||
NSForegroundColorAttributeName : [UIColor ows_materialBlueColor],
|
||||
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid)
|
||||
}];
|
||||
label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
|
||||
label.numberOfLines = 0;
|
||||
label.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.userInteractionEnabled = YES;
|
||||
[label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(forgotPinLinkTapped:)]];
|
||||
[self.view addSubview:label];
|
||||
return label;
|
||||
}
|
||||
|
||||
- (void)createSubmitButton
|
||||
{
|
||||
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(@"REGISTER_2FA_SUBMIT_BUTTON",
|
||||
@"Label for 'submit' button in the 2FA registration view.")
|
||||
font:[OWSFlatButton fontForHeight:kSubmitButtonHeight]
|
||||
titleColor:[UIColor whiteColor]
|
||||
backgroundColor:[UIColor ows_signalBrandBlueColor]
|
||||
target:self
|
||||
selector:@selector(submitButtonWasPressed)];
|
||||
self.submitButton = submitButton;
|
||||
[self.view addSubview:self.submitButton];
|
||||
[self.submitButton autoSetDimension:ALDimensionHeight toSize:kSubmitButtonHeight];
|
||||
}
|
||||
|
||||
- (CGFloat)hMargin
|
||||
{
|
||||
return 20.f;
|
||||
}
|
||||
|
||||
- (void)createContents
|
||||
{
|
||||
const CGFloat kVSpacing = 30.f;
|
||||
|
||||
NSString *instructionsText = NSLocalizedString(
|
||||
@"REGISTER_2FA_INSTRUCTIONS", @"Instructions to enter the 'two-factor auth pin' in the 2FA registration view.");
|
||||
UILabel *instructionsLabel = [self createLabelWithText:instructionsText];
|
||||
[instructionsLabel autoPinTopToSuperviewWithMargin:kVSpacing];
|
||||
[instructionsLabel autoPinWidthToSuperviewWithMargin:self.hMargin];
|
||||
|
||||
UILabel *createForgotLink = [self createForgotLink];
|
||||
[createForgotLink autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:instructionsLabel withOffset:5];
|
||||
[createForgotLink autoPinWidthToSuperviewWithMargin:self.hMargin];
|
||||
|
||||
[self createPinTextfield];
|
||||
[self.pinTextfield autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:createForgotLink withOffset:kVSpacing];
|
||||
[self.pinTextfield autoPinWidthToSuperviewWithMargin:self.hMargin];
|
||||
|
||||
UIView *underscoreView = [UIView new];
|
||||
underscoreView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:1.f];
|
||||
[self.view addSubview:underscoreView];
|
||||
[underscoreView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.pinTextfield withOffset:3];
|
||||
[underscoreView autoPinWidthToSuperviewWithMargin:self.hMargin];
|
||||
[underscoreView autoSetDimension:ALDimensionHeight toSize:1.f];
|
||||
|
||||
[self createSubmitButton];
|
||||
[self.submitButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:underscoreView withOffset:kVSpacing];
|
||||
[self.submitButton autoPinWidthToSuperviewWithMargin:self.hMargin];
|
||||
|
||||
[self updateEnabling];
|
||||
}
|
||||
|
||||
- (void)updateEnabling
|
||||
{
|
||||
[self.submitButton setEnabled:self.hasValidPin];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textField:(UITextField *)textField
|
||||
shouldChangeCharactersInRange:(NSRange)range
|
||||
replacementString:(NSString *)insertionText
|
||||
{
|
||||
|
||||
[ViewControllerUtils ows2FAPINTextField:textField
|
||||
shouldChangeCharactersInRange:range
|
||||
replacementString:insertionText];
|
||||
|
||||
[self updateEnabling];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Events
|
||||
|
||||
- (void)submitButtonWasPressed
|
||||
{
|
||||
OWSAssert(self.hasValidPin);
|
||||
|
||||
[self tryToRegister];
|
||||
}
|
||||
|
||||
- (BOOL)hasValidPin
|
||||
{
|
||||
return self.pinTextfield.text.length >= kMin2FAPinLength;
|
||||
}
|
||||
|
||||
- (void)tryToRegister
|
||||
{
|
||||
OWSAssert(self.hasValidPin);
|
||||
OWSAssert(self.entryView.hasValidPin);
|
||||
OWSAssert(self.verificationCode.length > 0);
|
||||
NSString *pin = self.pinTextfield.text;
|
||||
OWSAssert(pin.length > 0);
|
||||
OWSAssert(pinCode.length > 0);
|
||||
|
||||
DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
@ -235,10 +114,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
canCancel:NO
|
||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteringCode]);
|
||||
[self.accountManager registerWithVerificationCode:self.verificationCode pin:pin]
|
||||
[self.accountManager registerWithVerificationCode:self.verificationCode pin:pinCode]
|
||||
.then(^{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSProdInfo([OWSAnalyticsEvents registrationRegisteringSubmittedCode]);
|
||||
[[OWS2FAManager sharedManager] mark2FAAsEnabledWithPin:pinCode];
|
||||
|
||||
DDLogInfo(@"%@ Successfully registered Signal account.", weakSelf.logTag);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@ -263,7 +143,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@"register with 'two-factor auth' failed.")
|
||||
message:error.localizedDescription];
|
||||
|
||||
[weakSelf.pinTextfield becomeFirstResponder];
|
||||
[weakSelf.entryView makePinTextFieldFirstResponder];
|
||||
}];
|
||||
});
|
||||
});
|
||||
@ -275,16 +155,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[ProfileViewController presentForRegistration:self.navigationController];
|
||||
}
|
||||
|
||||
- (void)forgotPinLinkTapped:(UIGestureRecognizer *)sender
|
||||
{
|
||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
||||
[OWSAlerts
|
||||
showAlertWithTitle:nil
|
||||
message:NSLocalizedString(@"REGISTER_2FA_FORGOT_PIN_ALERT_MESSAGE",
|
||||
@"Alert message explaining what happens if you forget your 'two-factor auth pin'.")];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
110
Signal/src/ViewControllers/OWS2FAReminderViewController.swift
Normal file
110
Signal/src/ViewControllers/OWS2FAReminderViewController.swift
Normal file
@ -0,0 +1,110 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc
|
||||
public class OWS2FAReminderViewController: UIViewController, PinEntryViewDelegate {
|
||||
|
||||
private var ows2FAManager: OWS2FAManager {
|
||||
return OWS2FAManager.shared()
|
||||
}
|
||||
|
||||
var pinEntryView: PinEntryView!
|
||||
|
||||
@objc
|
||||
public class func wrappedInNavController() -> UINavigationController {
|
||||
let navController = UINavigationController()
|
||||
navController.pushViewController(OWS2FAReminderViewController(), animated: false)
|
||||
|
||||
return navController
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
pinEntryView.makePinTextFieldFirstResponder()
|
||||
}
|
||||
|
||||
override public func loadView() {
|
||||
assert(ows2FAManager.pinCode != nil)
|
||||
|
||||
self.navigationItem.title = NSLocalizedString("REMINDER_2FA_NAV_TITLE", comment: "Navbar title for when user is peridoically prompted to enter their registration lock PIN")
|
||||
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(didPressCloseButton))
|
||||
|
||||
let view = UIView()
|
||||
self.view = view
|
||||
view.backgroundColor = .white
|
||||
|
||||
let pinEntryView = PinEntryView()
|
||||
self.pinEntryView = pinEntryView
|
||||
pinEntryView.delegate = self
|
||||
let instructionsText = NSLocalizedString("REMINDER_2FA_BODY", comment: "Body text for when user is peridoically prompted to enter their registration lock PIN")
|
||||
pinEntryView.instructionsText = instructionsText
|
||||
|
||||
view.addSubview(pinEntryView)
|
||||
|
||||
pinEntryView.autoPinWidthToSuperview(withMargin: 20)
|
||||
pinEntryView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
|
||||
pinEntryView.autoPin(toBottomLayoutGuideOf: self, withInset: 0)
|
||||
}
|
||||
|
||||
// MARK: PinEntryViewDelegate
|
||||
public func pinEntryView(_ entryView: PinEntryView, submittedPinCode pinCode: String) {
|
||||
Logger.info("\(logTag) in \(#function)")
|
||||
if checkResult(pinCode: pinCode) {
|
||||
didSubmitCorrectPin()
|
||||
} else {
|
||||
didSubmitWrongPin()
|
||||
}
|
||||
}
|
||||
|
||||
//textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
public func pinEntryView(_ entryView: PinEntryView, pinCodeDidChange pinCode: String) {
|
||||
// optimistically match, without having to press "done"
|
||||
if checkResult(pinCode: pinCode) {
|
||||
didSubmitCorrectPin()
|
||||
}
|
||||
}
|
||||
|
||||
public func pinEntryViewForgotPinLinkTapped(_ entryView: PinEntryView) {
|
||||
Logger.info("\(logTag) in \(#function)")
|
||||
let alertBody = NSLocalizedString("REMINDER_2FA_FORGOT_PIN_ALERT_MESSAGE",
|
||||
comment: "Alert message explaining what happens if you forget your 'two-factor auth pin'")
|
||||
OWSAlerts.showAlert(title:nil, message:alertBody)
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
@objc
|
||||
private func didPressCloseButton(sender: UIButton) {
|
||||
Logger.info("\(logTag) in \(#function)")
|
||||
// We'll ask again next time they launch
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
|
||||
private func checkResult(pinCode: String) -> Bool {
|
||||
return pinCode == ows2FAManager.pinCode
|
||||
}
|
||||
|
||||
private func didSubmitCorrectPin() {
|
||||
Logger.info("\(logTag) in \(#function) noWrongGuesses: \(noWrongGuesses)")
|
||||
|
||||
self.dismiss(animated: true)
|
||||
|
||||
OWS2FAManager.shared().updateRepetitionInterval(withWasSuccessful: noWrongGuesses)
|
||||
}
|
||||
|
||||
var noWrongGuesses = true
|
||||
private func didSubmitWrongPin() {
|
||||
noWrongGuesses = false
|
||||
Logger.info("\(logTag) in \(#function)")
|
||||
let alertTitle = NSLocalizedString("REMINDER_2FA_WRONG_PIN_ALERT_TITLE",
|
||||
comment: "Alert title after wrong guess for 'two-factor auth pin' reminder activity")
|
||||
let alertBody = NSLocalizedString("REMINDER_2FA_WRONG_PIN_ALERT_BODY",
|
||||
comment: "Alert body after wrong guess for 'two-factor auth pin' reminder activity")
|
||||
OWSAlerts.showAlert(title: alertTitle, message: alertBody)
|
||||
self.pinEntryView.clearText()
|
||||
}
|
||||
}
|
||||
@ -392,7 +392,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
presentFromViewController:self
|
||||
canCancel:NO
|
||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||
[OWS2FAManager.sharedManager enable2FAWithPin:self.candidatePin
|
||||
[OWS2FAManager.sharedManager requestEnable2FAWithPin:self.candidatePin
|
||||
success:^{
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
[weakSelf showCompleteUI];
|
||||
@ -408,10 +408,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[weakSelf updateTableContents];
|
||||
|
||||
[OWSAlerts
|
||||
showAlertWithTitle:NSLocalizedString(@"ALERT_ERROR_TITLE", @"")
|
||||
message:NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA",
|
||||
showAlertWithTitle:NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA",
|
||||
@"Error indicating that attempt to enable 'two-factor "
|
||||
@"auth' failed.")];
|
||||
@"auth' failed.")
|
||||
message:error.localizedDescription];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
@ -1102,7 +1102,7 @@ protocol CallServiceObserver: class {
|
||||
// We don't need to worry about the user granting or remoting this permission
|
||||
// during a call while the app is in the background, because changing this
|
||||
// permission kills the app.
|
||||
OWSAlerts.showAlert(withTitle: NSLocalizedString("MISSING_CAMERA_PERMISSION_TITLE", comment: "Alert title when camera is not authorized"),
|
||||
OWSAlerts.showAlert(title: NSLocalizedString("MISSING_CAMERA_PERMISSION_TITLE", comment: "Alert title when camera is not authorized"),
|
||||
message: NSLocalizedString("MISSING_CAMERA_PERMISSION_MESSAGE", comment: "Alert body when camera is not authorized"))
|
||||
}
|
||||
})
|
||||
|
||||
30
Signal/src/views/PinEntryView.h
Normal file
30
Signal/src/views/PinEntryView.h
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class PinEntryView;
|
||||
|
||||
@protocol PinEntryViewDelegate <NSObject>
|
||||
|
||||
- (void)pinEntryView:(PinEntryView *)entryView submittedPinCode:(NSString *)pinCode;
|
||||
- (void)pinEntryViewForgotPinLinkTapped:(PinEntryView *)entryView;
|
||||
|
||||
@optional
|
||||
- (void)pinEntryView:(PinEntryView *)entryView pinCodeDidChange:(NSString *)pinCode;
|
||||
|
||||
@end
|
||||
|
||||
@interface PinEntryView : UIView
|
||||
|
||||
@property (nonatomic, weak, nullable) id<PinEntryViewDelegate> delegate;
|
||||
@property (nonatomic, readonly) BOOL hasValidPin;
|
||||
@property (nullable, nonatomic) NSString *instructionsText;
|
||||
|
||||
- (void)clearText;
|
||||
- (BOOL)makePinTextFieldFirstResponder;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
205
Signal/src/views/PinEntryView.m
Normal file
205
Signal/src/views/PinEntryView.m
Normal file
@ -0,0 +1,205 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PinEntryView.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <SignalMessaging/UIColor+OWS.h>
|
||||
#import <SignalMessaging/UIFont+OWS.h>
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
#import <SignalMessaging/ViewControllerUtils.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface PinEntryView () <UITextFieldDelegate>
|
||||
|
||||
@property (nonatomic) UITextField *pinTextfield;
|
||||
@property (nonatomic) OWSFlatButton *submitButton;
|
||||
@property (nonatomic) UILabel *instructionsLabel;
|
||||
|
||||
@end
|
||||
|
||||
@implementation PinEntryView : UIView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
[self createContents];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - view creation
|
||||
|
||||
- (UILabel *)createLabelWithText:(nullable NSString *)text
|
||||
{
|
||||
UILabel *label = [UILabel new];
|
||||
label.textColor = [UIColor blackColor];
|
||||
label.text = text;
|
||||
label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
|
||||
label.numberOfLines = 0;
|
||||
label.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
[self addSubview:label];
|
||||
return label;
|
||||
}
|
||||
|
||||
- (void)createPinTextfield
|
||||
{
|
||||
self.pinTextfield = [UITextField new];
|
||||
self.pinTextfield.textColor = [UIColor blackColor];
|
||||
self.pinTextfield.placeholder
|
||||
= NSLocalizedString(@"2FA_PIN_DEFAULT_TEXT", @"Text field placeholder when entering a 'two-factor auth pin'.");
|
||||
self.pinTextfield.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(30.f, 36.f)];
|
||||
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
|
||||
self.pinTextfield.keyboardType = UIKeyboardTypeNumberPad;
|
||||
self.pinTextfield.delegate = self;
|
||||
self.pinTextfield.secureTextEntry = YES;
|
||||
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
|
||||
[self addSubview:self.pinTextfield];
|
||||
}
|
||||
|
||||
- (UILabel *)createForgotLink
|
||||
{
|
||||
UILabel *label = [UILabel new];
|
||||
label.textColor = [UIColor ows_materialBlueColor];
|
||||
NSString *text = NSLocalizedString(
|
||||
@"REGISTER_2FA_FORGOT_PIN", @"Label for 'I forgot my PIN' link in the 2FA registration view.");
|
||||
label.attributedText = [[NSAttributedString alloc]
|
||||
initWithString:text
|
||||
attributes:@{
|
||||
NSForegroundColorAttributeName : [UIColor ows_materialBlueColor],
|
||||
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid)
|
||||
}];
|
||||
label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
|
||||
label.numberOfLines = 0;
|
||||
label.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.userInteractionEnabled = YES;
|
||||
[label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(forgotPinLinkTapped:)]];
|
||||
[self addSubview:label];
|
||||
return label;
|
||||
}
|
||||
|
||||
- (void)createSubmitButton
|
||||
{
|
||||
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(@"REGISTER_2FA_SUBMIT_BUTTON",
|
||||
@"Label for 'submit' button in the 2FA registration view.")
|
||||
font:[OWSFlatButton fontForHeight:kSubmitButtonHeight]
|
||||
titleColor:[UIColor whiteColor]
|
||||
backgroundColor:[UIColor ows_signalBrandBlueColor]
|
||||
target:self
|
||||
selector:@selector(submitButtonWasPressed)];
|
||||
self.submitButton = submitButton;
|
||||
[self addSubview:submitButton];
|
||||
[self.submitButton autoSetDimension:ALDimensionHeight toSize:kSubmitButtonHeight];
|
||||
}
|
||||
|
||||
- (nullable NSString *)instructionsText
|
||||
{
|
||||
return self.instructionsLabel.text;
|
||||
}
|
||||
|
||||
- (void)setInstructionsText:(nullable NSString *)instructionsText
|
||||
{
|
||||
self.instructionsLabel.text = instructionsText;
|
||||
}
|
||||
|
||||
- (void)createContents
|
||||
{
|
||||
const CGFloat kVSpacing = 30.f;
|
||||
|
||||
UILabel *instructionsLabel = [self createLabelWithText:nil];
|
||||
self.instructionsLabel = instructionsLabel;
|
||||
[instructionsLabel autoPinTopToSuperviewWithMargin:kVSpacing];
|
||||
[instructionsLabel autoPinWidthToSuperview];
|
||||
|
||||
UILabel *createForgotLink = [self createForgotLink];
|
||||
[createForgotLink autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:instructionsLabel withOffset:5];
|
||||
[createForgotLink autoPinWidthToSuperview];
|
||||
|
||||
[self createPinTextfield];
|
||||
[self.pinTextfield autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:createForgotLink withOffset:kVSpacing];
|
||||
[self.pinTextfield autoPinWidthToSuperview];
|
||||
|
||||
UIView *underscoreView = [UIView new];
|
||||
underscoreView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:1.f];
|
||||
[self addSubview:underscoreView];
|
||||
[underscoreView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.pinTextfield withOffset:3];
|
||||
[underscoreView autoPinWidthToSuperview];
|
||||
[underscoreView autoSetDimension:ALDimensionHeight toSize:1.f];
|
||||
|
||||
[self createSubmitButton];
|
||||
[self.submitButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:underscoreView withOffset:kVSpacing];
|
||||
[self.submitButton autoPinWidthToSuperview];
|
||||
[self updateIsSubmitEnabled];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textField:(UITextField *)textField
|
||||
shouldChangeCharactersInRange:(NSRange)range
|
||||
replacementString:(NSString *)insertionText
|
||||
{
|
||||
|
||||
[ViewControllerUtils ows2FAPINTextField:textField
|
||||
shouldChangeCharactersInRange:range
|
||||
replacementString:insertionText];
|
||||
|
||||
[self updateIsSubmitEnabled];
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(pinEntryView:pinCodeDidChange:)]) {
|
||||
[self.delegate pinEntryView:self pinCodeDidChange:textField.text];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)updateIsSubmitEnabled;
|
||||
{
|
||||
[self.submitButton setEnabled:self.hasValidPin];
|
||||
}
|
||||
|
||||
- (BOOL)makePinTextFieldFirstResponder
|
||||
{
|
||||
return [self.pinTextfield becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (BOOL)hasValidPin
|
||||
{
|
||||
return self.pinTextfield.text.length >= kMin2FAPinLength;
|
||||
}
|
||||
|
||||
- (void)clearText
|
||||
{
|
||||
self.pinTextfield.text = @"";
|
||||
[self updateIsSubmitEnabled];
|
||||
}
|
||||
|
||||
#pragma mark - Events
|
||||
|
||||
- (void)submitButtonWasPressed
|
||||
{
|
||||
[self.delegate pinEntryView:self submittedPinCode:self.pinTextfield.text];
|
||||
}
|
||||
|
||||
- (void)forgotPinLinkTapped:(UIGestureRecognizer *)sender
|
||||
{
|
||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
||||
[self.delegate pinEntryViewForgotPinLinkTapped:self];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -1468,6 +1468,21 @@
|
||||
/* No comment provided by engineer. */
|
||||
"RELAY_REGISTERED_ERROR_RECOVERY" = "The phone number you are trying to register has already been registered on another server, please unregister from there and try again.";
|
||||
|
||||
/* Body text for when user is peridoically prompted to enter their registration lock PIN */
|
||||
"REMINDER_2FA_BODY" = "Registration Lock is enabled for your phone number.";
|
||||
|
||||
/* Alert message explaining what happens if you forget your 'two-factor auth pin' */
|
||||
"REMINDER_2FA_FORGOT_PIN_ALERT_MESSAGE" = "Registration Lock helps protect your phone number from unauthorized registration attempts. This feature can be disabled at any time in your Signal privacy settings.";
|
||||
|
||||
/* Navbar title for when user is peridoically prompted to enter their registration lock PIN */
|
||||
"REMINDER_2FA_NAV_TITLE" = "Enter Your Registration Lock PIN";
|
||||
|
||||
/* Alert body after wrong guess for 'two-factor auth pin' reminder activity */
|
||||
"REMINDER_2FA_WRONG_PIN_ALERT_BODY" = "You can set a new PIN in your privacy settings.";
|
||||
|
||||
/* Alert title after wrong guess for 'two-factor auth pin' reminder activity */
|
||||
"REMINDER_2FA_WRONG_PIN_ALERT_TITLE" = "That is not the correct PIN.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"REREGISTER_FOR_PUSH" = "Re-register for push notifications";
|
||||
|
||||
|
||||
@ -44,3 +44,4 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[];
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
#import <SignalMessaging/UIViewController+OWS.h>
|
||||
#import <SignalMessaging/VersionMigrations.h>
|
||||
#import <SignalMessaging/ViewControllerUtils.h>
|
||||
|
||||
@ -23,17 +23,17 @@ import Foundation
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func showAlert(withTitle title: String) {
|
||||
self.showAlert(withTitle: title, message: nil, buttonTitle: nil)
|
||||
public class func showAlert(title: String) {
|
||||
self.showAlert(title: title, message: nil, buttonTitle: nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func showAlert(withTitle title: String?, message: String) {
|
||||
self.showAlert(withTitle: title, message: message, buttonTitle: nil)
|
||||
public class func showAlert(title: String?, message: String) {
|
||||
self.showAlert(title: title, message: message, buttonTitle: nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func showAlert(withTitle title: String?, message: String? = nil, buttonTitle: String? = nil, buttonAction: ((UIAlertAction) -> Void)? = nil) {
|
||||
public class func showAlert(title: String?, message: String? = nil, buttonTitle: String? = nil, buttonAction: ((UIAlertAction) -> Void)? = nil) {
|
||||
let actionTitle = buttonTitle ?? NSLocalizedString("OK", comment: "")
|
||||
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
@ -42,7 +42,7 @@ import Foundation
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func showConfirmationAlert(withTitle title: String, message: String? = nil, proceedTitle: String? = nil, proceedAction: @escaping (UIAlertAction) -> Void) {
|
||||
public class func showConfirmationAlert(title: String, message: String? = nil, proceedTitle: String? = nil, proceedAction: @escaping (UIAlertAction) -> Void) {
|
||||
assert(title.count > 0)
|
||||
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
@ -88,7 +88,7 @@ import Foundation
|
||||
|
||||
Environment.preferences().setIOSUpgradeNagDate(Date())
|
||||
|
||||
OWSAlerts.showAlert(withTitle: NSLocalizedString("UPGRADE_IOS_ALERT_TITLE",
|
||||
OWSAlerts.showAlert(title: NSLocalizedString("UPGRADE_IOS_ALERT_TITLE",
|
||||
comment: "Title for the alert indicating that user should upgrade iOS."),
|
||||
message: NSLocalizedString("UPGRADE_IOS_ALERT_MESSAGE",
|
||||
comment: "Message for the alert indicating that user should upgrade iOS."))
|
||||
|
||||
@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (BOOL)hasObjectForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue;
|
||||
- (double)doubleForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(double)defaultValue;
|
||||
- (int)intForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
@ -31,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (void)setObject:(id)object forKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (void)setDouble:(double)value forKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (void)removeObjectForKey:(NSString *)string inCollection:(NSString *)collection;
|
||||
- (void)setInt:(int)integer forKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
|
||||
@ -34,13 +34,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return object;
|
||||
}
|
||||
|
||||
- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection ofExpectedType:(Class) class {
|
||||
- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection ofExpectedType:(Class)class
|
||||
{
|
||||
id _Nullable value = [self objectForKey:key inCollection:collection];
|
||||
OWSAssert(!value || [value isKindOfClass:class]);
|
||||
return value;
|
||||
}
|
||||
|
||||
- (nullable NSDictionary *)dictionaryForKey : (NSString *)key inCollection : (NSString *)collection
|
||||
- (nullable NSDictionary *)dictionaryForKey:(NSString *)key inCollection:(NSString *)collection
|
||||
{
|
||||
return [self objectForKey:key inCollection:collection ofExpectedType:[NSDictionary class]];
|
||||
}
|
||||
@ -61,6 +62,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return value ? [value boolValue] : defaultValue;
|
||||
}
|
||||
|
||||
- (double)doubleForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(double)defaultValue
|
||||
{
|
||||
NSNumber *_Nullable value = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]];
|
||||
return value ? [value doubleValue] : defaultValue;
|
||||
}
|
||||
|
||||
- (nullable NSData *)dataForKey:(NSString *)key inCollection:(NSString *)collection
|
||||
{
|
||||
return [self objectForKey:key inCollection:collection ofExpectedType:[NSData class]];
|
||||
@ -117,7 +124,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (void)setObject:(id)object forKey:(NSString *)key inCollection:(NSString *)collection
|
||||
{
|
||||
OWSAssert(object);
|
||||
OWSAssert(key.length > 0);
|
||||
OWSAssert(collection.length > 0);
|
||||
|
||||
@ -134,6 +140,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[self setObject:@(value) forKey:key inCollection:collection];
|
||||
}
|
||||
|
||||
- (void)setDouble:(double)value forKey:(NSString *)key inCollection:(NSString *)collection
|
||||
{
|
||||
OWSAssert(key.length > 0);
|
||||
OWSAssert(collection.length > 0);
|
||||
|
||||
[self setObject:@(value) forKey:key inCollection:collection];
|
||||
}
|
||||
|
||||
- (void)removeObjectForKey:(NSString *)key inCollection:(NSString *)collection
|
||||
{
|
||||
OWSAssert(key.length > 0);
|
||||
|
||||
@ -16,14 +16,23 @@ typedef void (^OWS2FAFailure)(NSError *error);
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
- (BOOL)is2FAEnabled;
|
||||
@property (nullable, nonatomic, readonly) NSString *pinCode;
|
||||
|
||||
- (void)enable2FAWithPin:(NSString *)pin
|
||||
success:(nullable OWS2FASuccess)success
|
||||
failure:(nullable OWS2FAFailure)failure;
|
||||
- (BOOL)is2FAEnabled;
|
||||
- (BOOL)isDueForReminder;
|
||||
|
||||
// Request with service
|
||||
- (void)requestEnable2FAWithPin:(NSString *)pin
|
||||
success:(nullable OWS2FASuccess)success
|
||||
failure:(nullable OWS2FAFailure)failure;
|
||||
|
||||
// Sore local settings if, used during registration
|
||||
- (void)mark2FAAsEnabledWithPin:(NSString *)pin;
|
||||
|
||||
- (void)disable2FAWithSuccess:(nullable OWS2FASuccess)success failure:(nullable OWS2FAFailure)failure;
|
||||
|
||||
- (void)updateRepetitionIntervalWithWasSuccessful:(BOOL)wasSuccessful;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -15,6 +15,12 @@ NSString *const NSNotificationName_2FAStateDidChange = @"NSNotificationName_2FAS
|
||||
|
||||
NSString *const kOWS2FAManager_Collection = @"kOWS2FAManager_Collection";
|
||||
NSString *const kOWS2FAManager_IsEnabledKey = @"kOWS2FAManager_IsEnabledKey";
|
||||
NSString *const kOWS2FAManager_LastSuccessfulReminderDateKey = @"kOWS2FAManager_LastSuccessfulReminderDateKey";
|
||||
NSString *const kOWS2FAManager_PinCode = @"kOWS2FAManager_PinCode";
|
||||
NSString *const kOWS2FAManager_RepetitionInterval = @"kOWS2FAManager_RepetitionInterval";
|
||||
|
||||
const NSUInteger kHourSecs = 60 * 60;
|
||||
const NSUInteger kDaySecs = kHourSecs * 24;
|
||||
|
||||
@interface OWS2FAManager ()
|
||||
|
||||
@ -81,7 +87,15 @@ NSString *const kOWS2FAManager_IsEnabledKey = @"kOWS2FAManager_IsEnabledKey";
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
- (void)enable2FAWithPin:(NSString *)pin success:(nullable OWS2FASuccess)success failure:(nullable OWS2FAFailure)failure
|
||||
- (void)mark2FAAsEnabledWithPin:(NSString *)pin
|
||||
{
|
||||
[self setIs2FAEnabled:YES];
|
||||
[self storePinCode:pin];
|
||||
}
|
||||
|
||||
- (void)requestEnable2FAWithPin:(NSString *)pin
|
||||
success:(nullable OWS2FASuccess)success
|
||||
failure:(nullable OWS2FAFailure)failure
|
||||
{
|
||||
OWSAssert(pin.length > 0);
|
||||
OWSAssert(success);
|
||||
@ -92,8 +106,7 @@ NSString *const kOWS2FAManager_IsEnabledKey = @"kOWS2FAManager_IsEnabledKey";
|
||||
success:^(NSURLSessionDataTask *task, id responseObject) {
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
[self setIs2FAEnabled:YES];
|
||||
|
||||
[self mark2FAAsEnabledWithPin:pin];
|
||||
if (success) {
|
||||
success();
|
||||
}
|
||||
@ -129,6 +142,115 @@ NSString *const kOWS2FAManager_IsEnabledKey = @"kOWS2FAManager_IsEnabledKey";
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Reminders
|
||||
|
||||
- (void)storePinCode:(nullable NSString *)pinCode
|
||||
{
|
||||
[self.dbConnection setObject:pinCode forKey:kOWS2FAManager_PinCode inCollection:kOWS2FAManager_Collection];
|
||||
}
|
||||
|
||||
- (nullable NSString *)pinCode
|
||||
{
|
||||
return [self.dbConnection objectForKey:kOWS2FAManager_PinCode inCollection:kOWS2FAManager_Collection];
|
||||
}
|
||||
|
||||
- (nullable NSDate *)lastSuccessfulReminderDate
|
||||
{
|
||||
return [self.dbConnection dateForKey:kOWS2FAManager_LastSuccessfulReminderDateKey
|
||||
inCollection:kOWS2FAManager_Collection];
|
||||
}
|
||||
|
||||
- (void)setLastSuccessfulReminderDate:(nullable NSDate *)date
|
||||
{
|
||||
DDLogDebug(@"%@ Seting setLastSuccessfulReminderDate:%@", self.logTag, date);
|
||||
[self.dbConnection setDate:date
|
||||
forKey:kOWS2FAManager_LastSuccessfulReminderDateKey
|
||||
inCollection:kOWS2FAManager_Collection];
|
||||
}
|
||||
|
||||
- (BOOL)isDueForReminder
|
||||
{
|
||||
if (!self.is2FAEnabled) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return self.nextReminderDate.timeIntervalSinceNow < 0;
|
||||
}
|
||||
|
||||
- (NSDate *)nextReminderDate
|
||||
{
|
||||
NSDate *lastSuccessfulReminderDate = self.lastSuccessfulReminderDate ?: [NSDate distantPast];
|
||||
|
||||
return [lastSuccessfulReminderDate dateByAddingTimeInterval:self.repetitionInterval];
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)allRepetitionIntervals
|
||||
{
|
||||
// Keep sorted monotonically increasing.
|
||||
return @[
|
||||
@(6 * kHourSecs),
|
||||
@(12 * kHourSecs),
|
||||
@(1 * kDaySecs),
|
||||
@(3 * kDaySecs),
|
||||
@(7 * kDaySecs),
|
||||
];
|
||||
}
|
||||
|
||||
- (double)defaultRepetitionInterval
|
||||
{
|
||||
return self.allRepetitionIntervals.firstObject.doubleValue;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)repetitionInterval
|
||||
{
|
||||
return [self.dbConnection doubleForKey:kOWS2FAManager_RepetitionInterval
|
||||
inCollection:kOWS2FAManager_Collection
|
||||
defaultValue:self.defaultRepetitionInterval];
|
||||
}
|
||||
|
||||
- (void)updateRepetitionIntervalWithWasSuccessful:(BOOL)wasSuccessful
|
||||
{
|
||||
if (wasSuccessful) {
|
||||
self.lastSuccessfulReminderDate = [NSDate new];
|
||||
}
|
||||
|
||||
NSTimeInterval oldInterval = self.repetitionInterval;
|
||||
NSTimeInterval newInterval = [self adjustRepetitionInterval:oldInterval wasSuccessful:wasSuccessful];
|
||||
|
||||
DDLogInfo(@"%@ %@ guess. Updating repetition interval: %f -> %f",
|
||||
self.logTag,
|
||||
(wasSuccessful ? @"successful" : @"failed"),
|
||||
oldInterval,
|
||||
newInterval);
|
||||
[self.dbConnection setDouble:newInterval
|
||||
forKey:kOWS2FAManager_RepetitionInterval
|
||||
inCollection:kOWS2FAManager_Collection];
|
||||
}
|
||||
|
||||
- (NSTimeInterval)adjustRepetitionInterval:(NSTimeInterval)oldInterval wasSuccessful:(BOOL)wasSuccessful
|
||||
{
|
||||
NSArray<NSNumber *> *allIntervals = self.allRepetitionIntervals;
|
||||
|
||||
NSUInteger oldIndex =
|
||||
[allIntervals indexOfObjectPassingTest:^BOOL(NSNumber *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
|
||||
return oldInterval >= (NSTimeInterval)obj.doubleValue;
|
||||
}];
|
||||
|
||||
NSUInteger newIndex;
|
||||
if (wasSuccessful) {
|
||||
newIndex = oldIndex + 1;
|
||||
} else {
|
||||
newIndex = oldIndex - 1;
|
||||
}
|
||||
|
||||
// clamp to be valid
|
||||
newIndex = MAX(0, MIN(allIntervals.count - 1, newIndex));
|
||||
|
||||
NSTimeInterval newInterval = allIntervals[newIndex].doubleValue;
|
||||
return newInterval;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -492,7 +492,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
||||
|
||||
let alertTitle = NSLocalizedString("SHARE_EXTENSION_UNABLE_TO_BUILD_ATTACHMENT_ALERT_TITLE",
|
||||
comment: "Shown when trying to share content to a Signal user for the share extension. Followed by failure details.")
|
||||
OWSAlerts.showAlert(withTitle: alertTitle,
|
||||
OWSAlerts.showAlert(title: alertTitle,
|
||||
message: error.localizedDescription,
|
||||
buttonTitle: CommonStrings.cancelButton) { _ in
|
||||
strongSelf.shareViewWasCancelled()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user