mirror of https://github.com/oxen-io/session-ios
Merge branch 'mkirk/2fa-reminders'
commit
d516bcc29b
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
Loading…
Reference in New Issue