PAPasscodeViewController.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. //Copyright (c) 2012, Denis Hennessy (Peer Assembly - http://peerassembly.com)
  2. //All rights reserved.
  3. //
  4. //Redistribution and use in source and binary forms, with or without
  5. //modification, are permitted provided that the following conditions are met:
  6. // * Redistributions of source code must retain the above copyright
  7. // notice, this list of conditions and the following disclaimer.
  8. // * Redistributions in binary form must reproduce the above copyright
  9. // notice, this list of conditions and the following disclaimer in the
  10. // documentation and/or other materials provided with the distribution.
  11. // * Neither the name of Peer Assembly, Denis Hennessy nor the
  12. // names of its contributors may be used to endorse or promote products
  13. // derived from this software without specific prior written permission.
  14. //
  15. //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  16. //ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  17. //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. //DISCLAIMED. IN NO EVENT SHALL PEER ASSEMBLY OR DENIS HENNESSY BE LIABLE FOR ANY
  19. //DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  20. //(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  21. //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  22. //ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  24. //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. #import <QuartzCore/QuartzCore.h>
  26. #import "PAPasscodeViewController.h"
  27. #define NAVBAR_HEIGHT 66
  28. #define PROMPT_HEIGHT 90
  29. #define DIGIT_SPACING 25
  30. #define DIGIT_WIDTH 19
  31. #define DIGIT_HEIGHT 1
  32. #define MARKER_WIDTH 16
  33. #define MARKER_HEIGHT 1
  34. #define MARKER_X 1.5
  35. #define MARKER_Y -18
  36. #define MESSAGE_HEIGHT 90
  37. #define FAILED_LCAP 19
  38. #define FAILED_RCAP 19
  39. #define FAILED_HEIGHT 26
  40. #define FAILED_MARGIN 10
  41. #define TEXTFIELD_MARGIN 8
  42. #define SLIDE_DURATION 0.3
  43. @interface PAPasscodeViewController ()
  44. - (void)cancel:(id)sender;
  45. - (void)handleFailedAttempt;
  46. - (void)handleCompleteField;
  47. - (void)passcodeChanged:(id)sender;
  48. - (void)resetFailedAttempts;
  49. - (void)showFailedAttempts;
  50. - (void)showScreenForPhase:(NSInteger)phase animated:(BOOL)animated;
  51. @end
  52. @implementation PAPasscodeViewController
  53. - (id)initForAction:(PasscodeAction)action {
  54. self = [super init];
  55. if (self) {
  56. _action = action;
  57. switch (action) {
  58. case PasscodeActionSet:
  59. self.title = NSLocalizedString(@"PASSCODE_SET", nil);
  60. _enterPrompt = NSLocalizedString(@"ENTER_PASSCODE", nil);
  61. _confirmPrompt = NSLocalizedString(@"REENTER_PASSCODE", nil);
  62. break;
  63. case PasscodeActionEnter:
  64. self.title = NSLocalizedString(@"ENTER_PASSCODE", nil);
  65. _enterPrompt = NSLocalizedString(@"ENTER_PASSCODE", nil);
  66. break;
  67. case PasscodeActionChange:
  68. self.title = NSLocalizedString(@"PASSCODE_CHANGE", nil);
  69. _changePrompt = NSLocalizedString(@"PASSCODE_ENTER_OLD", nil);
  70. _enterPrompt = NSLocalizedString(@"PASSCODE_ENTER_NEW", nil);
  71. _confirmPrompt = NSLocalizedString(@"PASSCODE_REENTER_NEW", nil);
  72. break;
  73. }
  74. self.modalPresentationStyle = UIModalPresentationFormSheet;
  75. _simple = YES;
  76. }
  77. return self;
  78. }
  79. - (void)loadView {
  80. UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
  81. view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
  82. self.navigationController.navigationBarHidden = YES;
  83. UINavigationBar *navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, view.bounds.size.width, NAVBAR_HEIGHT)];
  84. navigationBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  85. navigationBar.items = @[self.navigationItem];
  86. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  87. navigationBar.barTintColor = [UIColor VLCOrangeTintColor];
  88. navigationBar.tintColor = [UIColor whiteColor];
  89. navigationBar.titleTextAttributes = @{ UITextAttributeTextColor : [UIColor whiteColor] };
  90. } else {
  91. [navigationBar setBackgroundImage:[UIImage imageNamed:@"navBarBackground"] forBarMetrics:UIBarMetricsDefault];
  92. navigationBar.translucent = NO;
  93. navigationBar.opaque = YES;
  94. }
  95. [view addSubview:navigationBar];
  96. contentView = [[UIView alloc] initWithFrame:CGRectMake(0, NAVBAR_HEIGHT, view.bounds.size.width, view.bounds.size.height-NAVBAR_HEIGHT)];
  97. contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
  98. if (_backgroundView) {
  99. [contentView addSubview:_backgroundView];
  100. }
  101. contentView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  102. [view addSubview:contentView];
  103. CGFloat panelWidth = DIGIT_WIDTH*4+DIGIT_SPACING*3;
  104. if (_simple) {
  105. UIView *digitPanel = [[UIView alloc] initWithFrame:CGRectMake(0, 0, panelWidth, DIGIT_HEIGHT)];
  106. digitPanel.frame = CGRectOffset(digitPanel.frame, (contentView.bounds.size.width - panelWidth) / 2., PROMPT_HEIGHT);
  107. digitPanel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
  108. [contentView addSubview:digitPanel];
  109. UIImage *backgroundImage = [UIImage imageNamed:@"papasscode_background"];
  110. UIImage *markerImage = [UIImage imageNamed:@"papasscode_marker"];
  111. CGFloat xLeft = 0;
  112. for (int i=0;i<4;i++) {
  113. UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage];
  114. backgroundImageView.frame = CGRectOffset(backgroundImageView.frame, xLeft, 0);
  115. [digitPanel addSubview:backgroundImageView];
  116. digitImageViews[i] = [[UIImageView alloc] initWithImage:markerImage];
  117. digitImageViews[i].autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
  118. digitImageViews[i].frame = CGRectOffset(digitImageViews[i].frame, backgroundImageView.frame.origin.x+MARKER_X, MARKER_Y);
  119. [digitPanel addSubview:digitImageViews[i]];
  120. xLeft += DIGIT_SPACING + backgroundImage.size.width;
  121. }
  122. passcodeTextField = [[UITextField alloc] initWithFrame:digitPanel.frame];
  123. passcodeTextField.hidden = YES;
  124. } else {
  125. UIView *passcodePanel = [[UIView alloc] initWithFrame:CGRectMake(0, 0, panelWidth, DIGIT_HEIGHT)];
  126. passcodePanel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
  127. passcodePanel.frame = CGRectOffset(passcodePanel.frame, (contentView.bounds.size.width-passcodePanel.bounds.size.width)/2, PROMPT_HEIGHT);
  128. passcodePanel.frame = CGRectInset(passcodePanel.frame, TEXTFIELD_MARGIN, TEXTFIELD_MARGIN);
  129. passcodePanel.layer.borderColor = [UIColor colorWithRed:0.65 green:0.67 blue:0.70 alpha:1.0].CGColor;
  130. passcodePanel.layer.borderWidth = 1.0;
  131. passcodePanel.layer.cornerRadius = 5.0;
  132. passcodePanel.layer.shadowColor = [UIColor whiteColor].CGColor;
  133. passcodePanel.layer.shadowOffset = CGSizeMake(0, 1);
  134. passcodePanel.layer.shadowOpacity = 1.0;
  135. passcodePanel.layer.shadowRadius = 1.0;
  136. passcodePanel.backgroundColor = [UIColor whiteColor];
  137. [contentView addSubview:passcodePanel];
  138. passcodeTextField = [[UITextField alloc] initWithFrame:CGRectInset(passcodePanel.frame, 6, 6)];
  139. }
  140. passcodeTextField.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
  141. passcodeTextField.borderStyle = UITextBorderStyleNone;
  142. passcodeTextField.secureTextEntry = YES;
  143. passcodeTextField.textColor = [UIColor colorWithRed:0.23 green:0.33 blue:0.52 alpha:1.0];
  144. passcodeTextField.keyboardType = UIKeyboardTypeNumberPad;
  145. [passcodeTextField addTarget:self action:@selector(passcodeChanged:) forControlEvents:UIControlEventEditingChanged];
  146. [contentView addSubview:passcodeTextField];
  147. promptLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, contentView.bounds.size.width, PROMPT_HEIGHT)];
  148. promptLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  149. promptLabel.backgroundColor = [UIColor clearColor];
  150. promptLabel.textColor = [UIColor VLCLightTextColor];
  151. promptLabel.font = [UIFont boldSystemFontOfSize:17];
  152. promptLabel.shadowColor = [UIColor VLCDarkTextShadowColor];
  153. promptLabel.shadowOffset = CGSizeMake(0, 1);
  154. promptLabel.textAlignment = NSTextAlignmentCenter;
  155. promptLabel.numberOfLines = 0;
  156. [contentView addSubview:promptLabel];
  157. messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, PROMPT_HEIGHT+DIGIT_HEIGHT, contentView.bounds.size.width, MESSAGE_HEIGHT)];
  158. messageLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  159. messageLabel.backgroundColor = [UIColor clearColor];
  160. messageLabel.textColor = [UIColor colorWithRed:0.30 green:0.34 blue:0.42 alpha:1.0];
  161. messageLabel.font = [UIFont systemFontOfSize:14];
  162. messageLabel.shadowColor = [UIColor whiteColor];
  163. messageLabel.shadowOffset = CGSizeMake(0, 1);
  164. messageLabel.textAlignment = NSTextAlignmentCenter;
  165. messageLabel.numberOfLines = 0;
  166. messageLabel.text = _message;
  167. [contentView addSubview:messageLabel];
  168. UIImage *failedBg = [[UIImage imageNamed:@"papasscode_failed_bg"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, FAILED_LCAP, 0, FAILED_RCAP)];
  169. failedImageView = [[UIImageView alloc] initWithImage:failedBg];
  170. failedImageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
  171. failedImageView.hidden = YES;
  172. [contentView addSubview:failedImageView];
  173. failedAttemptsLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  174. failedAttemptsLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
  175. failedAttemptsLabel.backgroundColor = [UIColor clearColor];
  176. failedAttemptsLabel.textColor = [UIColor whiteColor];
  177. failedAttemptsLabel.font = [UIFont boldSystemFontOfSize:15];
  178. failedAttemptsLabel.shadowColor = [UIColor blackColor];
  179. failedAttemptsLabel.shadowOffset = CGSizeMake(0, -1);
  180. failedAttemptsLabel.textAlignment = NSTextAlignmentCenter;
  181. failedAttemptsLabel.hidden = YES;
  182. [contentView addSubview:failedAttemptsLabel];
  183. self.view = view;
  184. }
  185. - (void)viewDidLoad {
  186. [super viewDidLoad];
  187. if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidCancel:)]) {
  188. if (_simple) {
  189. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)];
  190. } else {
  191. self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)];
  192. }
  193. }
  194. if (_failedAttempts > 0) {
  195. [self showFailedAttempts];
  196. }
  197. }
  198. - (void)viewWillAppear:(BOOL)animated {
  199. [super viewWillAppear:animated];
  200. [self showScreenForPhase:0 animated:NO];
  201. [passcodeTextField becomeFirstResponder];
  202. }
  203. - (void)viewWillDisappear:(BOOL)animated {
  204. [super viewWillDisappear:animated];
  205. [passcodeTextField resignFirstResponder];
  206. }
  207. - (NSUInteger)supportedInterfaceOrientations {
  208. return UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown;
  209. }
  210. - (void)cancel:(id)sender {
  211. [_delegate PAPasscodeViewControllerDidCancel:self];
  212. }
  213. #pragma mark - implementation helpers
  214. - (void)handleCompleteField {
  215. NSString *text = passcodeTextField.text;
  216. switch (_action) {
  217. case PasscodeActionSet:
  218. if (phase == 0) {
  219. _passcode = text;
  220. messageLabel.text = @"";
  221. [self showScreenForPhase:1 animated:YES];
  222. } else {
  223. if ([text isEqualToString:_passcode]) {
  224. if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidSetPasscode:)]) {
  225. [_delegate PAPasscodeViewControllerDidSetPasscode:self];
  226. }
  227. } else {
  228. [self showScreenForPhase:0 animated:YES];
  229. messageLabel.text = NSLocalizedString(@"PASSCODES_DID_NOT_MATCH", nil);
  230. }
  231. }
  232. break;
  233. case PasscodeActionEnter:
  234. if ([text isEqualToString:_passcode]) {
  235. [self resetFailedAttempts];
  236. if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidEnterPasscode:)]) {
  237. [_delegate PAPasscodeViewControllerDidEnterPasscode:self];
  238. }
  239. } else {
  240. if (_alternativePasscode && [text isEqualToString:_alternativePasscode]) {
  241. [self resetFailedAttempts];
  242. if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidEnterAlternativePasscode:)]) {
  243. [_delegate PAPasscodeViewControllerDidEnterAlternativePasscode:self];
  244. }
  245. } else {
  246. [self handleFailedAttempt];
  247. [self showScreenForPhase:0 animated:NO];
  248. }
  249. }
  250. break;
  251. case PasscodeActionChange:
  252. if (phase == 0) {
  253. if ([text isEqualToString:_passcode]) {
  254. [self resetFailedAttempts];
  255. [self showScreenForPhase:1 animated:YES];
  256. } else {
  257. [self handleFailedAttempt];
  258. [self showScreenForPhase:0 animated:NO];
  259. }
  260. } else if (phase == 1) {
  261. _passcode = text;
  262. messageLabel.text = @"";
  263. [self showScreenForPhase:2 animated:YES];
  264. } else {
  265. if ([text isEqualToString:_passcode]) {
  266. if ([_delegate respondsToSelector:@selector(PAPasscodeViewControllerDidChangePasscode:)]) {
  267. [_delegate PAPasscodeViewControllerDidChangePasscode:self];
  268. }
  269. } else {
  270. [self showScreenForPhase:1 animated:YES];
  271. messageLabel.text = NSLocalizedString(@"PASSCODES_DID_NOT_MATCH", nil);
  272. }
  273. }
  274. break;
  275. }
  276. }
  277. - (void)handleFailedAttempt {
  278. _failedAttempts++;
  279. [self showFailedAttempts];
  280. if ([_delegate respondsToSelector:@selector(PAPasscodeViewController:didFailToEnterPasscode:)]) {
  281. [_delegate PAPasscodeViewController:self didFailToEnterPasscode:_failedAttempts];
  282. }
  283. }
  284. - (void)resetFailedAttempts {
  285. messageLabel.hidden = NO;
  286. failedImageView.hidden = YES;
  287. failedAttemptsLabel.hidden = YES;
  288. _failedAttempts = 0;
  289. }
  290. - (void)showFailedAttempts {
  291. messageLabel.hidden = YES;
  292. failedImageView.hidden = NO;
  293. failedAttemptsLabel.hidden = NO;
  294. if (_failedAttempts == 1) {
  295. failedAttemptsLabel.text = NSLocalizedString(@"PASSCODE_FAILED_1", nil);
  296. } else {
  297. failedAttemptsLabel.text = [NSString stringWithFormat:NSLocalizedString(@"PASSCODE_FAILED_FORMAT", nil), _failedAttempts];
  298. }
  299. [failedAttemptsLabel sizeToFit];
  300. CGFloat bgWidth = failedAttemptsLabel.bounds.size.width + FAILED_MARGIN*2;
  301. CGFloat x = floor((contentView.bounds.size.width-bgWidth)/2);
  302. CGFloat y = PROMPT_HEIGHT+DIGIT_HEIGHT+floor((MESSAGE_HEIGHT-FAILED_HEIGHT)/2);
  303. failedImageView.frame = CGRectMake(x, y, bgWidth, FAILED_HEIGHT);
  304. x = failedImageView.frame.origin.x+FAILED_MARGIN;
  305. y = failedImageView.frame.origin.y+floor((failedImageView.bounds.size.height-failedAttemptsLabel.frame.size.height)/2);
  306. failedAttemptsLabel.frame = CGRectMake(x, y, failedAttemptsLabel.bounds.size.width, failedAttemptsLabel.bounds.size.height);
  307. }
  308. - (void)passcodeChanged:(id)sender {
  309. NSString *text = passcodeTextField.text;
  310. if (_simple) {
  311. if ([text length] > 4) {
  312. text = [text substringToIndex:4];
  313. }
  314. for (int i=0;i<4;i++) {
  315. digitImageViews[i].hidden = i >= [text length];
  316. }
  317. if ([text length] == 4) {
  318. [self handleCompleteField];
  319. }
  320. } else {
  321. self.navigationItem.rightBarButtonItem.enabled = [text length] > 0;
  322. }
  323. }
  324. - (void)showScreenForPhase:(NSInteger)newPhase animated:(BOOL)animated {
  325. CGFloat dir = (newPhase > phase) ? 1 : -1;
  326. if (animated) {
  327. UIGraphicsBeginImageContext(self.view.bounds.size);
  328. [contentView.layer renderInContext:UIGraphicsGetCurrentContext()];
  329. UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
  330. UIGraphicsEndImageContext();
  331. snapshotImageView = [[UIImageView alloc] initWithImage:snapshot];
  332. snapshotImageView.frame = CGRectOffset(snapshotImageView.frame, -contentView.frame.size.width*dir, 0);
  333. [contentView addSubview:snapshotImageView];
  334. }
  335. phase = newPhase;
  336. passcodeTextField.text = @"";
  337. if (!_simple) {
  338. BOOL finalScreen = _action == PasscodeActionSet && phase == 1;
  339. finalScreen |= _action == PasscodeActionEnter && phase == 0;
  340. finalScreen |= _action == PasscodeActionChange && phase == 2;
  341. if (finalScreen) {
  342. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(handleCompleteField)];
  343. } else {
  344. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BUTTON_NEXT", nil) style:UIBarButtonItemStyleBordered target:self action:@selector(handleCompleteField)];
  345. }
  346. self.navigationItem.rightBarButtonItem.enabled = NO;
  347. }
  348. switch (_action) {
  349. case PasscodeActionSet:
  350. if (phase == 0) {
  351. promptLabel.text = _enterPrompt;
  352. } else {
  353. promptLabel.text = _confirmPrompt;
  354. }
  355. break;
  356. case PasscodeActionEnter:
  357. promptLabel.text = _enterPrompt;
  358. break;
  359. case PasscodeActionChange:
  360. if (phase == 0) {
  361. promptLabel.text = _changePrompt;
  362. } else if (phase == 1) {
  363. promptLabel.text = _enterPrompt;
  364. } else {
  365. promptLabel.text = _confirmPrompt;
  366. }
  367. break;
  368. }
  369. for (int i=0;i<4;i++) {
  370. digitImageViews[i].hidden = YES;
  371. }
  372. if (animated) {
  373. contentView.frame = CGRectOffset(contentView.frame, contentView.frame.size.width*dir, 0);
  374. [UIView animateWithDuration:SLIDE_DURATION animations:^() {
  375. contentView.frame = CGRectOffset(contentView.frame, -contentView.frame.size.width*dir, 0);
  376. } completion:^(BOOL finished) {
  377. [snapshotImageView removeFromSuperview];
  378. snapshotImageView = nil;
  379. }];
  380. }
  381. }
  382. @end