//Copyright (c) 2012, Denis Hennessy (Peer Assembly - http://peerassembly.com) //All rights reserved. // //Redistribution and use in source and binary forms, with or without //modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Peer Assembly, Denis Hennessy nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND //ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE //DISCLAIMED. IN NO EVENT SHALL PEER ASSEMBLY OR DENIS HENNESSY BE LIABLE FOR ANY //DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES //(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND //ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import "PAPasscodeViewController.h" #define NAVBAR_HEIGHT 66 #define PROMPT_HEIGHT 90 #define DIGIT_SPACING 25 #define DIGIT_WIDTH 19 #define DIGIT_HEIGHT 1 #define MARKER_WIDTH 16 #define MARKER_HEIGHT 1 #define MARKER_X 1.5 #define MARKER_Y -18 #define MESSAGE_HEIGHT 90 #define FAILED_LCAP 19 #define FAILED_RCAP 19 #define FAILED_HEIGHT 26 #define FAILED_MARGIN 10 #define TEXTFIELD_MARGIN 8 #define SLIDE_DURATION 0.3 @interface PAPasscodeViewController () - (void)cancel:(id)sender; - (void)handleFailedAttempt; - (void)handleCompleteField; - (void)passcodeChanged:(id)sender; - (void)resetFailedAttempts; - (void)showFailedAttempts; - (void)showScreenForPhase:(NSInteger)phase animated:(BOOL)animated; @end @implementation PAPasscodeViewController - (id)initForAction:(PasscodeAction)action { self = [super init]; if (self) { _action = action; switch (action) { case PasscodeActionSet: self.title = NSLocalizedString(@"PASSCODE_SET", nil); _enterPrompt = NSLocalizedString(@"ENTER_PASSCODE", nil); _confirmPrompt = NSLocalizedString(@"REENTER_PASSCODE", nil); break; case PasscodeActionEnter: self.title = NSLocalizedString(@"ENTER_PASSCODE", nil); _enterPrompt = NSLocalizedString(@"ENTER_PASSCODE", nil); break; case PasscodeActionChange: self.title = NSLocalizedString(@"PASSCODE_CHANGE", nil); _changePrompt = NSLocalizedString(@"PASSCODE_ENTER_OLD", nil); _enterPrompt = NSLocalizedString(@"PASSCODE_ENTER_NEW", nil); _confirmPrompt = NSLocalizedString(@"PASSCODE_REENTER_NEW", nil); break; } self.modalPresentationStyle = UIModalPresentationFormSheet; _simple = YES; } return self; } - (void)loadView { UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame]; view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; self.navigationController.navigationBarHidden = YES; UINavigationBar *navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, view.bounds.size.width, NAVBAR_HEIGHT)]; navigationBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; navigationBar.items = @[self.navigationItem]; navigationBar.barTintColor = [UIColor VLCOrangeTintColor]; navigationBar.tintColor = [UIColor whiteColor]; navigationBar.titleTextAttributes = @{ NSForegroundColorAttributeName : [UIColor whiteColor] }; [view addSubview:navigationBar]; contentView = [[UIView alloc] initWithFrame:CGRectMake(0, NAVBAR_HEIGHT, view.bounds.size.width, view.bounds.size.height-NAVBAR_HEIGHT)]; contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; if (_backgroundView) { [contentView addSubview:_backgroundView]; } contentView.backgroundColor = [UIColor VLCDarkBackgroundColor]; [view addSubview:contentView]; CGFloat panelWidth = DIGIT_WIDTH*4+DIGIT_SPACING*3; if (_simple) { UIView *digitPanel = [[UIView alloc] initWithFrame:CGRectMake(0, 0, panelWidth, DIGIT_HEIGHT)]; digitPanel.frame = CGRectOffset(digitPanel.frame, (contentView.bounds.size.width - panelWidth) / 2., PROMPT_HEIGHT); digitPanel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; [contentView addSubview:digitPanel]; UIImage *backgroundImage = [UIImage imageNamed:@"papasscode_background"]; UIImage *markerImage = [UIImage imageNamed:@"papasscode_marker"]; CGFloat xLeft = 0; for (int i=0;i<4;i++) { UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage]; backgroundImageView.frame = CGRectOffset(backgroundImageView.frame, xLeft, 0); [digitPanel addSubview:backgroundImageView]; digitImageViews[i] = [[UIImageView alloc] initWithImage:markerImage]; digitImageViews[i].autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; digitImageViews[i].frame = CGRectOffset(digitImageViews[i].frame, backgroundImageView.frame.origin.x+MARKER_X, MARKER_Y); [digitPanel addSubview:digitImageViews[i]]; xLeft += DIGIT_SPACING + backgroundImage.size.width; } passcodeTextField = [[UITextField alloc] initWithFrame:digitPanel.frame]; passcodeTextField.hidden = YES; } else { UIView *passcodePanel = [[UIView alloc] initWithFrame:CGRectMake(0, 0, panelWidth, DIGIT_HEIGHT)]; passcodePanel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; passcodePanel.frame = CGRectOffset(passcodePanel.frame, (contentView.bounds.size.width-passcodePanel.bounds.size.width)/2, PROMPT_HEIGHT); passcodePanel.frame = CGRectInset(passcodePanel.frame, TEXTFIELD_MARGIN, TEXTFIELD_MARGIN); passcodePanel.layer.borderColor = [UIColor colorWithRed:0.65 green:0.67 blue:0.70 alpha:1.0].CGColor; passcodePanel.layer.borderWidth = 1.0; passcodePanel.layer.cornerRadius = 5.0; passcodePanel.layer.shadowColor = [UIColor whiteColor].CGColor; passcodePanel.layer.shadowOffset = CGSizeMake(0, 1); passcodePanel.layer.shadowOpacity = 1.0; passcodePanel.layer.shadowRadius = 1.0; passcodePanel.backgroundColor = [UIColor whiteColor]; [contentView addSubview:passcodePanel]; passcodeTextField = [[UITextField alloc] initWithFrame:CGRectInset(passcodePanel.frame, 6, 6)]; } passcodeTextField.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; passcodeTextField.borderStyle = UITextBorderStyleNone; passcodeTextField.secureTextEntry = YES; passcodeTextField.textColor = [UIColor colorWithRed:0.23 green:0.33 blue:0.52 alpha:1.0]; passcodeTextField.keyboardType = UIKeyboardTypeNumberPad; [passcodeTextField addTarget:self action:@selector(passcodeChanged:) forControlEvents:UIControlEventEditingChanged]; [contentView addSubview:passcodeTextField]; promptLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, contentView.bounds.size.width, PROMPT_HEIGHT)]; promptLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; promptLabel.backgroundColor = [UIColor clearColor]; promptLabel.textColor = [UIColor VLCLightTextColor]; promptLabel.font = [UIFont boldSystemFontOfSize:17]; promptLabel.shadowColor = [UIColor VLCDarkTextShadowColor]; promptLabel.shadowOffset = CGSizeMake(0, 1); promptLabel.textAlignment = NSTextAlignmentCenter; promptLabel.numberOfLines = 0; [contentView addSubview:promptLabel]; messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, PROMPT_HEIGHT+DIGIT_HEIGHT, contentView.bounds.size.width, MESSAGE_HEIGHT)]; messageLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; messageLabel.backgroundColor = [UIColor clearColor]; messageLabel.textColor = [UIColor VLCLightTextColor]; messageLabel.font = [UIFont boldSystemFontOfSize:14]; messageLabel.shadowColor = [UIColor VLCDarkTextShadowColor]; messageLabel.shadowOffset = CGSizeMake(0, 1); messageLabel.textAlignment = NSTextAlignmentCenter; messageLabel.numberOfLines = 0; messageLabel.text = _message; [contentView addSubview:messageLabel]; UIImage *failedBg = [[UIImage imageNamed:@"papasscode_failed_bg"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, FAILED_LCAP, 0, FAILED_RCAP)]; failedImageView = [[UIImageView alloc] initWithImage:failedBg]; failedImageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; failedImageView.hidden = YES; [contentView addSubview:failedImageView]; failedAttemptsLabel = [[UILabel alloc] initWithFrame:CGRectZero]; failedAttemptsLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; failedAttemptsLabel.backgroundColor = [UIColor clearColor]; failedAttemptsLabel.textColor = [UIColor whiteColor]; failedAttemptsLabel.font = [UIFont boldSystemFontOfSize:15]; failedAttemptsLabel.shadowColor = [UIColor blackColor]; failedAttemptsLabel.shadowOffset = CGSizeMake(0, -1); failedAttemptsLabel.textAlignment = NSTextAlignmentCenter; failedAttemptsLabel.hidden = YES; [contentView addSubview:failedAttemptsLabel]; self.view = view; } - (void)viewDidLoad { [super viewDidLoad]; if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidCancel:)]) { if (_simple) { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)]; } else { self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)]; } } if (_failedAttempts > 0) { [self showFailedAttempts]; } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self showScreenForPhase:0 animated:NO]; [passcodeTextField becomeFirstResponder]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [passcodeTextField resignFirstResponder]; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown; } - (void)cancel:(id)sender { [_delegate PAPasscodeViewControllerDidCancel:self]; } #pragma mark - implementation helpers - (void)handleCompleteField { NSString *text = passcodeTextField.text; switch (_action) { case PasscodeActionSet: if (phase == 0) { _passcode = text; messageLabel.text = @""; [self showScreenForPhase:1 animated:YES]; } else { if ([text isEqualToString:_passcode]) { if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidSetPasscode:)]) { [_delegate PAPasscodeViewControllerDidSetPasscode:self]; } } else { [self showScreenForPhase:0 animated:YES]; messageLabel.text = NSLocalizedString(@"PASSCODES_DID_NOT_MATCH", nil); } } break; case PasscodeActionEnter: if ([text isEqualToString:_passcode]) { [self resetFailedAttempts]; if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidEnterPasscode:)]) { [_delegate PAPasscodeViewControllerDidEnterPasscode:self]; } } else { if (_alternativePasscode && [text isEqualToString:_alternativePasscode]) { [self resetFailedAttempts]; if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidEnterAlternativePasscode:)]) { [_delegate PAPasscodeViewControllerDidEnterAlternativePasscode:self]; } } else { [self handleFailedAttempt]; [self showScreenForPhase:0 animated:NO]; } } break; case PasscodeActionChange: if (phase == 0) { if ([text isEqualToString:_passcode]) { [self resetFailedAttempts]; [self showScreenForPhase:1 animated:YES]; } else { [self handleFailedAttempt]; [self showScreenForPhase:0 animated:NO]; } } else if (phase == 1) { _passcode = text; messageLabel.text = @""; [self showScreenForPhase:2 animated:YES]; } else { if ([text isEqualToString:_passcode]) { if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidChangePasscode:)]) { [_delegate PAPasscodeViewControllerDidChangePasscode:self]; } } else { [self showScreenForPhase:1 animated:YES]; messageLabel.text = NSLocalizedString(@"PASSCODES_DID_NOT_MATCH", nil); } } break; } } - (void)handleFailedAttempt { _failedAttempts++; [self showFailedAttempts]; if ([_delegate respondsToSelector:@selector(PAPasscodeViewController:didFailToEnterPasscode:)]) { [_delegate PAPasscodeViewController:self didFailToEnterPasscode:_failedAttempts]; } } - (void)resetFailedAttempts { messageLabel.hidden = NO; failedImageView.hidden = YES; failedAttemptsLabel.hidden = YES; _failedAttempts = 0; } - (void)showFailedAttempts { messageLabel.hidden = YES; failedImageView.hidden = NO; failedAttemptsLabel.hidden = NO; if (_failedAttempts == 1) { failedAttemptsLabel.text = NSLocalizedString(@"PASSCODE_FAILED_1", nil); } else { failedAttemptsLabel.text = [NSString stringWithFormat:NSLocalizedString(@"PASSCODE_FAILED_FORMAT", nil), _failedAttempts]; } [failedAttemptsLabel sizeToFit]; CGFloat bgWidth = failedAttemptsLabel.bounds.size.width + FAILED_MARGIN*2; CGFloat x = floor((contentView.bounds.size.width-bgWidth)/2); CGFloat y = PROMPT_HEIGHT+DIGIT_HEIGHT+floor((MESSAGE_HEIGHT-FAILED_HEIGHT)/2); failedImageView.frame = CGRectMake(x, y, bgWidth, FAILED_HEIGHT); x = failedImageView.frame.origin.x+FAILED_MARGIN; y = failedImageView.frame.origin.y+floor((failedImageView.bounds.size.height-failedAttemptsLabel.frame.size.height)/2); failedAttemptsLabel.frame = CGRectMake(x, y, failedAttemptsLabel.bounds.size.width, failedAttemptsLabel.bounds.size.height); } - (void)passcodeChanged:(id)sender { NSString *text = passcodeTextField.text; if (_simple) { if ([text length] > 4) { text = [text substringToIndex:4]; } for (int i=0;i<4;i++) { digitImageViews[i].hidden = i >= [text length]; } if ([text length] == 4) { [self handleCompleteField]; } } else { self.navigationItem.rightBarButtonItem.enabled = [text length] > 0; } } - (void)showScreenForPhase:(NSInteger)newPhase animated:(BOOL)animated { CGFloat dir = (newPhase > phase) ? 1 : -1; if (animated) { UIGraphicsBeginImageContext(self.view.bounds.size); [contentView.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); snapshotImageView = [[UIImageView alloc] initWithImage:snapshot]; snapshotImageView.frame = CGRectOffset(snapshotImageView.frame, -contentView.frame.size.width*dir, 0); [contentView addSubview:snapshotImageView]; } phase = newPhase; passcodeTextField.text = @""; if (!_simple) { BOOL finalScreen = _action == PasscodeActionSet && phase == 1; finalScreen |= _action == PasscodeActionEnter && phase == 0; finalScreen |= _action == PasscodeActionChange && phase == 2; if (finalScreen) { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(handleCompleteField)]; } else { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BUTTON_NEXT", nil) style:UIBarButtonItemStylePlain target:self action:@selector(handleCompleteField)]; } self.navigationItem.rightBarButtonItem.enabled = NO; } switch (_action) { case PasscodeActionSet: if (phase == 0) { promptLabel.text = _enterPrompt; } else { promptLabel.text = _confirmPrompt; } break; case PasscodeActionEnter: promptLabel.text = _enterPrompt; break; case PasscodeActionChange: if (phase == 0) { promptLabel.text = _changePrompt; } else if (phase == 1) { promptLabel.text = _enterPrompt; } else { promptLabel.text = _confirmPrompt; } break; } for (int i=0;i<4;i++) { digitImageViews[i].hidden = YES; } if (animated) { contentView.frame = CGRectOffset(contentView.frame, contentView.frame.size.width*dir, 0); [UIView animateWithDuration:SLIDE_DURATION animations:^() { contentView.frame = CGRectOffset(contentView.frame, -contentView.frame.size.width*dir, 0); } completion:^(BOOL finished) { [snapshotImageView removeFromSuperview]; snapshotImageView = nil; }]; } } @end