Merge branch 'mkirk/2fa-reminders'

pull/1/head
Michael Kirk 7 years ago
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];
}
- (UILabel *)createLabelWithText:(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.view 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.view 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.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];
[self.entryView makePinTextFieldFirstResponder];
}
#pragma mark - UITextFieldDelegate
#pragma mark - PinEntryViewDelegate
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
- (void)pinEntryView:(PinEntryView *)entryView submittedPinCode:(NSString *)pinCode
{
OWSAssert(self.entryView.hasValidPin);
[ViewControllerUtils ows2FAPINTextField:textField
shouldChangeCharactersInRange:range
replacementString:insertionText];
[self updateEnabling];
return NO;
[self tryToRegisterWithPinCode:pinCode];
}
#pragma mark - Events
- (void)submitButtonWasPressed
- (void)pinEntryViewForgotPinLinkTapped:(PinEntryView *)entryView
{
OWSAssert(self.hasValidPin);
[self tryToRegister];
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];
}
- (BOOL)hasValidPin
{
return self.pinTextfield.text.length >= kMin2FAPinLength;
}
#pragma mark - Registration
- (void)tryToRegister
- (void)tryToRegisterWithPinCode:(NSString *)pinCode
{
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

@ -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"))
}
})

@ -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

@ -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;
@property (nullable, nonatomic, readonly) NSString *pinCode;
- (BOOL)is2FAEnabled;
- (BOOL)isDueForReminder;
// Request with service
- (void)requestEnable2FAWithPin:(NSString *)pin
success:(nullable OWS2FASuccess)success
failure:(nullable OWS2FAFailure)failure;
- (void)enable2FAWithPin:(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…
Cancel
Save