1
0
mirror of https://github.com/danog/Telegram.git synced 2025-01-22 05:52:06 +01:00
Telegram/Telegraph/TGPasscodeEntryController.m
2015-10-01 19:19:52 +03:00

561 lines
21 KiB
Objective-C

#import "TGPasscodeEntryController.h"
#import "TGPasswordEntryView.h"
#import "TGStringUtils.h"
#import "TGTimerTarget.h"
#import <AudioToolbox/AudioToolbox.h>
#import <LocalAuthentication/LocalAuthentication.h>
typedef enum {
TGPasscodeEntryControllerSubmodeEnteringCurrent,
TGPasscodeEntryControllerSubmodeEnteringNew,
TGPasscodeEntryControllerSubmodeReenteringNew
} TGPasscodeEntryControllerSubmode;
@interface TGPasscodeEntryController ()
{
TGPasscodeEntryControllerStyle _style;
TGPasscodeEntryControllerMode _mode;
TGPasscodeEntryControllerSubmode _submode;
bool _cancelEnabled;
NSString *_candidatePasscode;
TGPasswordEntryView *_view;
UIBarButtonItem *_nextItem;
bool _usingTouchId;
bool _alternativeMethodSelected;
NSTimer *_shouldWaitTimer;
bool _keepStatusBarStyle;
}
@end
@implementation TGPasscodeEntryController
- (instancetype)initWithStyle:(TGPasscodeEntryControllerStyle)style mode:(TGPasscodeEntryControllerMode)mode cancelEnabled:(bool)cancelEnabled allowTouchId:(bool)allowTouchId completion:(void (^)(NSString *))completion
{
self = [super init];
if (self != nil)
{
_style = style;
_mode = mode;
_completion = [completion copy];
_allowTouchId = allowTouchId;
_cancelEnabled = cancelEnabled;
_keepStatusBarStyle = true;
switch (_mode)
{
case TGPasscodeEntryControllerModeVerifySimple:
case TGPasscodeEntryControllerModeVerifyComplex:
{
break;
}
case TGPasscodeEntryControllerModeSetupSimple:
case TGPasscodeEntryControllerModeSetupComplex:
{
_submode = TGPasscodeEntryControllerSubmodeEnteringNew;
break;
}
case TGPasscodeEntryControllerModeChangeSimpleToSimple:
case TGPasscodeEntryControllerModeChangeSimpleToComplex:
case TGPasscodeEntryControllerModeChangeComplexToSimple:
case TGPasscodeEntryControllerModeChangeComplexToComplex:
{
_submode = TGPasscodeEntryControllerSubmodeEnteringCurrent;
break;
}
}
self.navigationBarShouldBeHidden = true;
if ([self invalidPasscodeAttempts] >= 6 && ![self shouldWaitBeforeAttempting])
{
[self resetInvalidPasscodeAttempts];
}
[self resetMode:_mode];
_shouldWaitTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(checkShouldWait) interval:1.0 repeat:true];
}
return self;
}
- (void)dealloc
{
[_shouldWaitTimer invalidate];
_shouldWaitTimer = nil;
}
- (void)cancelPressed
{
if (_completion)
_completion(nil);
[_view resignFirstResponder];
}
- (void)nextPressed
{
[self passcodeEntered:[_view passcode]];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return _keepStatusBarStyle ? UIStatusBarStyleLightContent : UIStatusBarStyleDefault;
}
- (TGPasswordEntryViewStyle)_passwordEntryViewStyleForStyle:(TGPasscodeEntryControllerStyle)style
{
switch (style)
{
case TGPasscodeEntryControllerStyleDefault:
return TGPasswordEntryViewStyleDefault;
case TGPasscodeEntryControllerStyleTranslucent:
return TGPasswordEntryViewStyleTranslucent;
}
}
- (void)loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
CGSize screenSize = self.view.frame.size;
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
{
screenSize = [UIScreen mainScreen].bounds.size;
if (screenSize.width > screenSize.height)
{
CGFloat tmp = screenSize.width;
screenSize.width = screenSize.height;
screenSize.height = tmp;
}
}
_view = [[TGPasswordEntryView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, screenSize.width, screenSize.height) style:[self _passwordEntryViewStyleForStyle:_style]];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
_view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
__weak TGPasscodeEntryController *weakSelf = self;
if (_cancelEnabled)
{
_view.cancel = ^
{
__strong TGPasscodeEntryController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf cancelPressed];
};
}
_view.simplePasscodeEntered = ^
{
__strong TGPasscodeEntryController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf passcodeEntered:[strongSelf->_view passcode]];
};
_view.complexPasscodeEntered = ^
{
__strong TGPasscodeEntryController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf passcodeEntered:[strongSelf->_view passcode]];
};
_view.passcodeChanged = ^(NSString *passcode)
{
__strong TGPasscodeEntryController *strongSelf = weakSelf;
if (strongSelf != nil)
strongSelf->_nextItem.enabled = passcode.length != 0;
};
[self.view addSubview:_view];
[self resetMode:_mode];
[_view becomeFirstResponder];
}
- (bool)supportsTouchId
{
if (iosMajorVersion() >= 8)
{
if ([[[LAContext alloc] init] canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil])
return true;
}
return false;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
/*if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
{
UIView *snapshotView = [_view snapshotViewAfterScreenUpdates:false];
snapshotView.frame = _view.frame;
[self.view addSubview:snapshotView];
[UIView animateWithDuration:duration animations:^
{
snapshotView.alpha = 0.0f;
snapshotView.frame = (CGRect){CGPointZero, [self referenceViewSizeForOrientation:toInterfaceOrientation]};
} completion:^(__unused BOOL finished)
{
[snapshotView removeFromSuperview];
}];
}*/
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)prepareForAppear
{
[_view updateBackgroundIfNeeded];
_keepStatusBarStyle = true;
if (iosMajorVersion() >= 7)
[self setNeedsStatusBarAppearanceUpdate];
}
- (void)prepareForDisappear
{
_keepStatusBarStyle = false;
if (iosMajorVersion() >= 7)
[self setNeedsStatusBarAppearanceUpdate];
}
- (void)refreshTouchId
{
[_view resignFirstResponder];
[_view becomeFirstResponder];
if (!_usingTouchId && !_alternativeMethodSelected && _allowTouchId && [self supportsTouchId] && _touchIdCompletion)
{
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error])
{
_usingTouchId = true;
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:TGLocalized(@"EnterPasscode.TouchId") reply:^(BOOL success, NSError *error)
{
if (error != nil)
{
TGDispatchOnMainThread(^
{
_usingTouchId = false;
_alternativeMethodSelected = true;
});
}
else
{
if (success)
{
TGDispatchOnMainThread(^
{
_usingTouchId = false;
if (_touchIdCompletion)
_touchIdCompletion();
});
}
else
{
TGDispatchOnMainThread(^
{
_usingTouchId = false;
});
}
}
}];
}
}
}
- (NSInteger)invalidPasscodeAttempts
{
return [[[NSUserDefaults standardUserDefaults] objectForKey:@"Passcode_invalidAttempts"] integerValue];
}
- (void)addInvalidPasscodeAttempt
{
[[NSUserDefaults standardUserDefaults] setObject:@([self invalidPasscodeAttempts] + 1) forKey:@"Passcode_invalidAttempts"];
[[NSUserDefaults standardUserDefaults] setObject:@([[NSDate date] timeIntervalSince1970]) forKey:@"Passcode_invalidAttemptDate"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)resetInvalidPasscodeAttempts
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"Passcode_invalidAttempts"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"Passcode_invalidAttemptDate"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (bool)shouldWaitBeforeAttempting
{
if ([self invalidPasscodeAttempts] < 6)
return false;
NSTimeInterval invalidAttemptDate = [[[NSUserDefaults standardUserDefaults] objectForKey:@"Passcode_invalidAttemptDate"] doubleValue];
NSTimeInterval waitInterval = 60.0;
#ifdef DEBUG
waitInterval = 5.0;
#endif
return [[NSDate date] timeIntervalSince1970] - invalidAttemptDate < waitInterval;
}
- (NSTimeInterval)intervalSinceLastInvalidPasscodeAttempt
{
NSTimeInterval invalidAttemptDate = [[[NSUserDefaults standardUserDefaults] objectForKey:@"Passcode_invalidAttemptDate"] doubleValue];
return [[NSDate date] timeIntervalSince1970] - invalidAttemptDate;
}
- (void)checkShouldWait
{
if ([self invalidPasscodeAttempts] >= 6 && ![self shouldWaitBeforeAttempting])
{
[self resetInvalidPasscodeAttempts];
}
[_view setErrorTitle:[self currentErrorText]];
}
- (NSString *)currentErrorText
{
NSInteger attemptCount = [self invalidPasscodeAttempts];
if (attemptCount == 0)
return @"";
else if (attemptCount < 6)
{
NSString *format = [TGStringUtils integerValueFormat:@"PasscodeSettings.FailedAttempts_" value:attemptCount];
return [[NSString alloc] initWithFormat:TGLocalized(format), [[NSString alloc] initWithFormat:@"%d",(int)attemptCount]];
}
else
return TGLocalized(@"PasscodeSettings.TryAgainIn1Minute");
}
- (void)resetMode:(TGPasscodeEntryControllerMode)mode
{
_candidatePasscode = nil;
_mode = mode;
switch (_mode)
{
case TGPasscodeEntryControllerModeVerifySimple:
case TGPasscodeEntryControllerModeVerifyComplex:
{
[_view setTitle:TGLocalized(@"EnterPasscode.EnterPasscode") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeVerifyComplex animated:false];
if (_mode == TGPasscodeEntryControllerModeVerifySimple)
{
if (_cancelEnabled)
{
[self setRightBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(cancelPressed)]];
}
}
else if (_mode == TGPasscodeEntryControllerModeVerifyComplex)
{
if (_cancelEnabled)
{
[self setLeftBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(cancelPressed)]];
}
_nextItem = [[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Next") style:UIBarButtonItemStyleDone target:self action:@selector(nextPressed)];
[self setRightBarButtonItem:_nextItem];
_nextItem.enabled = false;
}
break;
}
case TGPasscodeEntryControllerModeSetupSimple:
case TGPasscodeEntryControllerModeSetupComplex:
{
[_view setTitle:TGLocalized(@"EnterPasscode.EnterNewPasscodeNew") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeSetupComplex animated:false];
[self setLeftBarButtonItem:nil];
[self setRightBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(cancelPressed)]];
break;
}
case TGPasscodeEntryControllerModeChangeComplexToSimple:
case TGPasscodeEntryControllerModeChangeComplexToComplex:
case TGPasscodeEntryControllerModeChangeSimpleToSimple:
case TGPasscodeEntryControllerModeChangeSimpleToComplex:
{
bool isComplex = _mode == TGPasscodeEntryControllerModeChangeComplexToSimple || _mode == TGPasscodeEntryControllerModeChangeComplexToComplex;
[_view setTitle:TGLocalized(@"EnterPasscode.EnterCurrentPasscode") errorTitle:[self currentErrorText] isComplex:isComplex animated:false];
if (_mode == TGPasscodeEntryControllerModeChangeSimpleToSimple)
{
[self setRightBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(cancelPressed)]];
}
else
{
[self setLeftBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(cancelPressed)]];
}
_nextItem = [[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Next") style:UIBarButtonItemStyleDone target:self action:@selector(nextPressed)];
if (_mode == TGPasscodeEntryControllerModeChangeComplexToSimple || _mode == TGPasscodeEntryControllerModeChangeComplexToComplex)
{
[self setRightBarButtonItem:_nextItem];
}
_nextItem.enabled = false;
break;
}
}
[_view becomeFirstResponder];
}
- (void)passcodeEntered:(NSString *)passcode
{
if ([self shouldWaitBeforeAttempting])
{
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
[_view resetPasscode];
return;
}
else if ([self invalidPasscodeAttempts] >= 6)
{
[self resetInvalidPasscodeAttempts];
}
switch (_mode)
{
case TGPasscodeEntryControllerModeVerifySimple:
case TGPasscodeEntryControllerModeVerifyComplex:
{
_alternativeMethodSelected = false;
if (_checkCurrentPasscode)
{
if (_checkCurrentPasscode(passcode))
{
[self resetInvalidPasscodeAttempts];
if (_completion)
_completion(passcode);
}
else
{
[self addInvalidPasscodeAttempt];
[_view setTitle:TGLocalized(@"EnterPasscode.EnterPasscode") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeVerifyComplex animated:false];
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
}
else
{
if (_completion)
_completion(passcode);
}
break;
}
case TGPasscodeEntryControllerModeSetupSimple:
case TGPasscodeEntryControllerModeSetupComplex:
{
switch (_submode)
{
case TGPasscodeEntryControllerSubmodeEnteringNew:
{
_candidatePasscode = passcode;
_submode = TGPasscodeEntryControllerSubmodeReenteringNew;
[_view setTitle:TGLocalized(@"EnterPasscode.RepeatNewPasscode") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeSetupComplex animated:true];
break;
}
case TGPasscodeEntryControllerSubmodeReenteringNew:
{
if ([passcode isEqualToString:_candidatePasscode])
{
if (_completion)
_completion(passcode);
}
else
{
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
_candidatePasscode = nil;
_submode = TGPasscodeEntryControllerSubmodeEnteringNew;
[_view setTitle:TGLocalized(@"EnterPasscode.EnterNewPasscodeNew") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeSetupComplex animated:true];
}
break;
}
default:
break;
}
break;
}
case TGPasscodeEntryControllerModeChangeSimpleToSimple:
case TGPasscodeEntryControllerModeChangeSimpleToComplex:
case TGPasscodeEntryControllerModeChangeComplexToSimple:
case TGPasscodeEntryControllerModeChangeComplexToComplex:
{
switch (_submode)
{
case TGPasscodeEntryControllerSubmodeEnteringCurrent:
{
if (_checkCurrentPasscode && _checkCurrentPasscode(passcode))
{
[self resetInvalidPasscodeAttempts];
_candidatePasscode = nil;
_submode = TGPasscodeEntryControllerSubmodeEnteringNew;
[_view setTitle:TGLocalized(@"EnterPasscode.EnterNewPasscodeChange") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeChangeSimpleToComplex || _mode == TGPasscodeEntryControllerModeChangeComplexToComplex animated:true];
if (_mode == TGPasscodeEntryControllerModeChangeSimpleToComplex || _mode == TGPasscodeEntryControllerModeChangeComplexToComplex)
[self setRightBarButtonItem:_nextItem];
else
[self setRightBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(cancelPressed)]];
}
else
{
[self addInvalidPasscodeAttempt];
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
_candidatePasscode = nil;
_submode = TGPasscodeEntryControllerSubmodeEnteringCurrent;
[_view setTitle:TGLocalized(@"EnterPasscode.EnterCurrentPasscode") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeChangeComplexToSimple || _mode == TGPasscodeEntryControllerModeChangeComplexToComplex animated:true];
}
break;
}
case TGPasscodeEntryControllerSubmodeEnteringNew:
{
_candidatePasscode = passcode;
_submode = TGPasscodeEntryControllerSubmodeReenteringNew;
[_view setTitle:TGLocalized(@"EnterPasscode.RepeatNewPasscode") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeChangeSimpleToComplex || _mode == TGPasscodeEntryControllerModeChangeComplexToComplex animated:true];
break;
}
case TGPasscodeEntryControllerSubmodeReenteringNew:
{
if ([_candidatePasscode isEqualToString:passcode])
{
if (_completion)
_completion(passcode);
}
else
{
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
_candidatePasscode = nil;
_submode = TGPasscodeEntryControllerSubmodeEnteringNew;
[_view setTitle:TGLocalized(@"EnterPasscode.EnterNewPasscodeChange") errorTitle:[self currentErrorText] isComplex:_mode == TGPasscodeEntryControllerModeChangeSimpleToComplex || _mode == TGPasscodeEntryControllerModeChangeComplexToComplex animated:true];
}
break;
}
}
break;
}
}
}
@end