diff --git a/MasterPassword/ObjC/iOS/MPAppsViewController.m b/MasterPassword/ObjC/iOS/MPAppsViewController.m
index 75820638..b0b0d78a 100644
--- a/MasterPassword/ObjC/iOS/MPAppsViewController.m
+++ b/MasterPassword/ObjC/iOS/MPAppsViewController.m
@@ -60,9 +60,6 @@
[self.pagePositionView removeFromSuperview];
[self.pageViewController didMoveToParentViewController:self];
- [self.pageViewController setViewControllers:@[[self.pageVCs objectAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward
- animated:NO completion:nil];
-
[super viewDidLoad];
}
@@ -75,6 +72,9 @@
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointApps attributes:nil];
+ [self.pageViewController setViewControllers:@[ [self.pageVCs objectAtIndex:1] ] direction:UIPageViewControllerNavigationDirectionForward
+ animated:NO completion:nil];
+
[super viewWillAppear:animated];
}
@@ -82,6 +82,9 @@
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Apps"];
+ [self.pageViewController setViewControllers:@[ [self.pageVCs objectAtIndex:0] ] direction:UIPageViewControllerNavigationDirectionForward
+ animated:YES completion:nil];
+
[super viewDidAppear:animated];
}
diff --git a/MasterPassword/ObjC/iOS/MPUnlockViewController.h b/MasterPassword/ObjC/iOS/MPUnlockViewController.h
index 8f483d70..452b2338 100644
--- a/MasterPassword/ObjC/iOS/MPUnlockViewController.h
+++ b/MasterPassword/ObjC/iOS/MPUnlockViewController.h
@@ -31,9 +31,11 @@
@property (weak, nonatomic) IBOutlet UITextField *emergencyMasterPassword;
@property (weak, nonatomic) IBOutlet UITextField *emergencySite;
@property (weak, nonatomic) IBOutlet UIStepper *emergencyCounterStepper;
-@property (weak, nonatomic) IBOutlet UISegmentedControl *emergencyType;
+@property (weak, nonatomic) IBOutlet UISegmentedControl *emergencyTypeControl;
@property (weak, nonatomic) IBOutlet UILabel *emergencyCounter;
-@property (weak, nonatomic) IBOutlet UITextField *emergencyPassword;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *emergencyActivity;
+@property (weak, nonatomic) IBOutlet UIButton *emergencyPassword;
+@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
@property (nonatomic, strong) UIColor *avatarShadowColor;
@@ -43,6 +45,7 @@
- (IBAction)google:(UIButton *)sender;
- (IBAction)mail:(UIButton *)sender;
- (IBAction)add:(UIButton *)sender;
-- (IBAction)closeEmergency;
+- (IBAction)emergencyClose:(UIButton *)sender;
+- (IBAction)emergencyCopy:(UIButton *)sender;
@end
diff --git a/MasterPassword/ObjC/iOS/MPUnlockViewController.m b/MasterPassword/ObjC/iOS/MPUnlockViewController.m
index d5ba73ad..61f730c9 100644
--- a/MasterPassword/ObjC/iOS/MPUnlockViewController.m
+++ b/MasterPassword/ObjC/iOS/MPUnlockViewController.m
@@ -23,6 +23,9 @@
@property(nonatomic, strong) NSOperationQueue *emergencyQueue;
@property(nonatomic, strong) MPKey *emergencyKey;
+@property(nonatomic, strong) NSTimer *marqueeTipTimer;
+@property(nonatomic) NSUInteger marqueeTipTextIndex;
+@property(nonatomic, strong) NSArray *marqueeTipTexts;
@end
@implementation MPUnlockViewController {
@@ -126,18 +129,20 @@
self.spinner.alpha = 0;
self.passwordTipView.hidden = NO;
self.createPasswordTipView.hidden = NO;
- self.emergencyPassword.text = @"";
+ [self.emergencyPassword setTitle:@"" forState:UIControlStateNormal];
self.emergencyGeneratorContainer.alpha = 0;
self.emergencyGeneratorContainer.hidden = YES;
self.emergencyQueue = [NSOperationQueue new];
[self.emergencyCounterStepper addTargetBlock:^(id sender, UIControlEvents event) {
self.emergencyCounter.text = PearlString( @"%d", (NSUInteger)self.emergencyCounterStepper.value);
-
+
[self updateEmergencyPassword];
} forControlEvents:UIControlEventValueChanged];
- [self.emergencyType addTargetBlock:^(id sender, UIControlEvents event) {
+ [self.emergencyTypeControl addTargetBlock:^(id sender, UIControlEvents event) {
[self updateEmergencyPassword];
- } forControlEvents:UIControlEventValueChanged];
+ } forControlEvents:UIControlEventValueChanged];
+ self.marqueeTipTexts = @[ @"Tap and hold to delete or reset user.",
+ @"Shake for emergency generator." ];
NSMutableArray *wordListLines = [NSMutableArray arrayWithCapacity:27413];
[[[NSString alloc] initWithData:[NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"dictionary" withExtension:@"lst"]]
@@ -163,7 +168,7 @@
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil
usingBlock:^(NSNotification *note) {
- [self closeEmergencyAnimated:NO];
+ [self emergencyCloseAnimated:NO];
self.uiContainer.alpha = 0;
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil
@@ -197,7 +202,6 @@
- (void)viewDidAppear:(BOOL)animated {
- dbg(@"Lock screen did appear: %@", animated? @"animated": @"not animated");
if (!animated)
[[self findTargetedAvatar] setSelected:YES];
else
@@ -207,6 +211,9 @@
self.uiContainer.alpha = 1;
}];
+ [self.marqueeTipTimer invalidate];
+ self.marqueeTipTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(marqueeTip) userInfo:nil repeats:YES];
+
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Unlock"];
[super viewDidAppear:animated];
@@ -215,7 +222,9 @@
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Lock screen will disappear");
- [self closeEmergencyAnimated:animated];
+ [self emergencyCloseAnimated:animated];
+
+ [self.marqueeTipTimer invalidate];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
@@ -237,7 +246,7 @@
self.emergencyGeneratorContainer.alpha = 0;
self.emergencyGeneratorContainer.hidden = NO;
- [UIView animateWithDuration:1 animations:^{
+ [UIView animateWithDuration:0.5 animations:^{
self.emergencyGeneratorContainer.alpha = 1;
} completion:^(BOOL finished) {
[self.emergencyName becomeFirstResponder];
@@ -245,14 +254,26 @@
}
}
+- (void)marqueeTip {
+ [UIView animateWithDuration:0.5 animations:^{
+ self.tip.alpha = 0;
+ } completion:^(BOOL finished) {
+ if (!finished)
+ return;
+
+ self.tip.text = self.marqueeTipTexts[++self.marqueeTipTextIndex % [self.marqueeTipTexts count]];
+ [UIView animateWithDuration:0.5 animations:^{
+ self.tip.alpha = 1;
+ }];
+ }];
+}
+
- (void)updateUsers {
NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady];
if (!moc)
return;
- self.tip.text = @"Tap and hold to delete or reset user.";
-
__block NSArray *users = nil;
[moc performBlockAndWait:^{
NSError *error = nil;
@@ -794,7 +815,8 @@
return;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
- self.emergencyPassword.text = @"...";
+ [self.emergencyActivity startAnimating];
+ [self.emergencyPassword setTitle:@"" forState:UIControlStateNormal];
NSString *masterPassword = self.emergencyMasterPassword.text;
NSString *userName = self.emergencyName.text;
@@ -807,71 +829,101 @@
}];
}
+- (MPElementType)emergencyType {
+
+ switch (self.emergencyTypeControl.selectedSegmentIndex) {
+ case 0:
+ return MPElementTypeGeneratedMaximum;
+ case 1:
+ return MPElementTypeGeneratedLong;
+ case 2:
+ return MPElementTypeGeneratedMedium;
+ case 3:
+ return MPElementTypeGeneratedBasic;
+ case 4:
+ return MPElementTypeGeneratedShort;
+ case 5:
+ return MPElementTypeGeneratedPIN;
+ default:
+ Throw(@"Unsupported type index: %d", self.emergencyTypeControl.selectedSegmentIndex);
+ }
+}
+
- (void)updateEmergencyPassword {
if (!self.emergencyKey || ![self.emergencySite.text length])
return;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
- self.emergencyPassword.text = @"...";
+ [self.emergencyPassword setTitle:@"" forState:UIControlStateNormal];
NSString *name = self.emergencySite.text;
NSUInteger counter = (NSUInteger)self.emergencyCounterStepper.value;
- MPElementType type;
- switch (self.emergencyType.selectedSegmentIndex) {
- case 0:
- type = MPElementTypeGeneratedMaximum;
- break;
- case 1:
- type = MPElementTypeGeneratedLong;
- break;
- case 2:
- type = MPElementTypeGeneratedMedium;
- break;
- case 3:
- type = MPElementTypeGeneratedBasic;
- break;
- case 4:
- type = MPElementTypeGeneratedShort;
- break;
- case 5:
- type = MPElementTypeGeneratedPIN;
- break;
- default:
- Throw(@"Unsupported type index: %d", self.emergencyType.selectedSegmentIndex);
- }
[self.emergencyQueue addOperationWithBlock:^{
- NSString *content = [MPAlgorithmDefault generateContentNamed:name ofType:type withCounter:counter usingKey:self.emergencyKey];
+ NSString *content = [MPAlgorithmDefault generateContentNamed:name ofType:[self emergencyType]
+ withCounter:counter usingKey:self.emergencyKey];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
- self.emergencyPassword.text = content;
+ [self.emergencyActivity stopAnimating];
+ [self.emergencyPassword setTitle:content forState:UIControlStateNormal];
}];
}];
}];
}
-- (IBAction)closeEmergency {
+- (IBAction)emergencyClose:(UIButton *)sender {
- [self closeEmergencyAnimated:YES];
+ [self emergencyCloseAnimated:YES];
}
-- (void)closeEmergencyAnimated:(BOOL)animated {
+- (IBAction)emergencyCopy:(UIButton *)sender {
+ inf(@"Copying emergency password for: %@", self.emergencyName.text);
+ [UIPasteboard generalPasteboard].string = [self.emergencyPassword titleForState:UIControlStateNormal];
+
+ [UIView animateWithDuration:0.3f animations:^{
+ self.contentTipContainer.alpha = 1;
+ } completion:^(BOOL finished) {
+ if (finished) {
+ dispatch_time_t popTime = dispatch_time( DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC );
+ dispatch_after( popTime, dispatch_get_main_queue(), ^(void) {
+ [UIView animateWithDuration:0.2f animations:^{
+ self.contentTipContainer.alpha = 0;
+ }];
+ } );
+ }
+ }];
+
+#ifdef TESTFLIGHT_SDK_VERSION
+ [TestFlight passCheckpoint:MPCheckpointCopyToPasteboard];
+#endif
+ [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard attributes:@{
+ @"type" : [MPAlgorithmDefault nameOfType:self.emergencyType],
+ @"version" : @MPAlgorithmDefaultVersion,
+ @"emergency" : @YES,
+ }];
+}
+
+- (void)emergencyCloseAnimated:(BOOL)animated {
+
[[self.emergencyGeneratorContainer findFirstResponderInHierarchy] resignFirstResponder];
if (animated) {
[UIView animateWithDuration:0.5 animations:^{
- [self closeEmergencyAnimated:NO];
+ self.emergencyGeneratorContainer.alpha = 0;
+ } completion:^(BOOL finished) {
+ [self emergencyCloseAnimated:NO];
}];
return;
}
-
+
self.emergencyName.text = @"";
self.emergencyMasterPassword.text = @"";
self.emergencySite.text = @"";
self.emergencyCounterStepper.value = 0;
- self.emergencyPassword.text = @"";
+ [self.emergencyPassword setTitle:@"" forState:UIControlStateNormal];
+ [self.emergencyActivity stopAnimating];
self.emergencyGeneratorContainer.alpha = 0;
self.emergencyGeneratorContainer.hidden = YES;
}
diff --git a/MasterPassword/ObjC/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/ObjC/iOS/MainStoryboard_iPhone.storyboard
index 06b334bb..d1b6e531 100644
--- a/MasterPassword/ObjC/iOS/MainStoryboard_iPhone.storyboard
+++ b/MasterPassword/ObjC/iOS/MainStoryboard_iPhone.storyboard
@@ -1513,6 +1513,19 @@ You could use the word wall for inspiration in finding a memorable master passw
+
+
+
+
+
+
+
+
+
+
+
+
+