diff --git a/External/Pearl b/External/Pearl
index e9dd56bc..3a61ba22 160000
--- a/External/Pearl
+++ b/External/Pearl
@@ -1 +1 @@
-Subproject commit e9dd56bc64735ebd28eef6c6dc748f480acc7c67
+Subproject commit 3a61ba22afefb23c2f87a3836e8b996d2b5a96fb
diff --git a/MasterPassword/MPAppDelegate_Key.m b/MasterPassword/MPAppDelegate_Key.m
index e9df9faa..6c1f43c6 100644
--- a/MasterPassword/MPAppDelegate_Key.m
+++ b/MasterPassword/MPAppDelegate_Key.m
@@ -81,6 +81,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
user.keyID = tryKeyID;
[[MPAppDelegate_Shared get] saveContext];
}
+ user.lastUsed = [NSDate date];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered];
diff --git a/MasterPassword/MPTypes.h b/MasterPassword/MPTypes.h
index 24b4f36c..f2b9aab2 100644
--- a/MasterPassword/MPTypes.h
+++ b/MasterPassword/MPTypes.h
@@ -31,11 +31,12 @@ typedef enum {
} MPElementFeature;
typedef enum {
- MPElementTypeGeneratedLong = 0x0 | MPElementTypeClassGenerated | 0x0,
- MPElementTypeGeneratedMedium = 0x1 | MPElementTypeClassGenerated | 0x0,
- MPElementTypeGeneratedShort = 0x2 | MPElementTypeClassGenerated | 0x0,
- MPElementTypeGeneratedBasic = 0x3 | MPElementTypeClassGenerated | 0x0,
- MPElementTypeGeneratedPIN = 0x4 | MPElementTypeClassGenerated | 0x0,
+ MPElementTypeGeneratedSecure = 0x0 | MPElementTypeClassGenerated | 0x0,
+ MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
+ MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
+ MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
+ MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
+ MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
diff --git a/MasterPassword/MPTypes.m b/MasterPassword/MPTypes.m
index a211aedc..28153baa 100644
--- a/MasterPassword/MPTypes.m
+++ b/MasterPassword/MPTypes.m
@@ -40,9 +40,12 @@ NSString *NSStringFromMPElementType(MPElementType type) {
return nil;
switch (type) {
+ case MPElementTypeGeneratedSecure:
+ return @"Secure Password";
+
case MPElementTypeGeneratedLong:
return @"Long Password";
-
+
case MPElementTypeGeneratedMedium:
return @"Medium Password";
@@ -72,9 +75,12 @@ Class ClassFromMPElementType(MPElementType type) {
return nil;
switch (type) {
- case MPElementTypeGeneratedLong:
+ case MPElementTypeGeneratedSecure:
return [MPElementGeneratedEntity class];
+ case MPElementTypeGeneratedLong:
+ return [MPElementGeneratedEntity class];
+
case MPElementTypeGeneratedMedium:
return [MPElementGeneratedEntity class];
@@ -135,7 +141,7 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui
[name dataUsingEncoding:NSUTF8StringEncoding],
key,
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)],
- nil] hashWith:PearlDigestSHA1];
+ nil] hashWith:PearlDigestSHA256];
trc(@"seed is: %@", seed);
const char *seedBytes = seed.bytes;
diff --git a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword.xcdatamodel/contents b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword.xcdatamodel/contents
index fa986b6f..9e3fd78f 100644
--- a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword.xcdatamodel/contents
+++ b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword.xcdatamodel/contents
@@ -4,7 +4,7 @@
-
+
@@ -15,17 +15,17 @@
-
+
-
-
+
+
-
+
-
+
\ No newline at end of file
diff --git a/MasterPassword/iOS/MPAppDelegate.m b/MasterPassword/iOS/MPAppDelegate.m
index cccd5261..66e0f0f5 100644
--- a/MasterPassword/iOS/MPAppDelegate.m
+++ b/MasterPassword/iOS/MPAppDelegate.m
@@ -516,7 +516,7 @@
- (NSString *)testFlightToken {
- return NullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
+ return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
}
@@ -535,7 +535,7 @@
- (NSString *)crashlyticsAPIKey {
- return NullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
+ return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
}
@@ -554,7 +554,7 @@
- (NSString *)apptentiveAPIKey {
- return NullToNil([[self apptentiveInfo] valueForKeyPath:@"API Key"]);
+ return NSNullToNil([[self apptentiveInfo] valueForKeyPath:@"API Key"]);
}
@@ -574,7 +574,7 @@
- (NSString *)localyticsKey {
#ifdef DEBUG
- return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
+ return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
#else
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
#endif
diff --git a/MasterPassword/iOS/MPPreferencesViewController.m b/MasterPassword/iOS/MPPreferencesViewController.m
index 4c89c728..cdfbf71b 100644
--- a/MasterPassword/iOS/MPPreferencesViewController.m
+++ b/MasterPassword/iOS/MPPreferencesViewController.m
@@ -98,6 +98,8 @@
else if (cell == self.changeMPCell)
[[MPAppDelegate get] changeMP];
+
+ [tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - IASKSettingsDelegate
diff --git a/MasterPassword/iOS/MPSearchDelegate.m b/MasterPassword/iOS/MPSearchDelegate.m
index 7b71e7c0..28bf5414 100644
--- a/MasterPassword/iOS/MPSearchDelegate.m
+++ b/MasterPassword/iOS/MPSearchDelegate.m
@@ -131,7 +131,7 @@
assert(self.query);
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
- self.query, self.query, NilToNull([MPAppDelegate get].activeUser)];
+ self.query, self.query, NilToNSNull([MPAppDelegate get].activeUser)];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
diff --git a/MasterPassword/iOS/MPTypeViewController.m b/MasterPassword/iOS/MPTypeViewController.m
index 8bd82627..8e1ec781 100644
--- a/MasterPassword/iOS/MPTypeViewController.m
+++ b/MasterPassword/iOS/MPTypeViewController.m
@@ -90,14 +90,18 @@
// Generated
switch (indexPath.row) {
case 0:
- return MPElementTypeGeneratedLong;
+ return NSNotFound;
case 1:
- return MPElementTypeGeneratedMedium;
+ return MPElementTypeGeneratedSecure;
case 2:
- return MPElementTypeGeneratedShort;
+ return MPElementTypeGeneratedLong;
case 3:
- return MPElementTypeGeneratedBasic;
+ return MPElementTypeGeneratedMedium;
case 4:
+ return MPElementTypeGeneratedShort;
+ case 5:
+ return MPElementTypeGeneratedBasic;
+ case 6:
return MPElementTypeGeneratedPIN;
default:
@@ -109,8 +113,10 @@
// Stored
switch (indexPath.row) {
case 0:
- return MPElementTypeStoredPersonal;
+ return NSNotFound;
case 1:
+ return MPElementTypeStoredPersonal;
+ case 2:
return MPElementTypeStoredDevicePrivate;
default:
diff --git a/MasterPassword/iOS/MPUnlockViewController.h b/MasterPassword/iOS/MPUnlockViewController.h
index b7c89187..ac924e30 100644
--- a/MasterPassword/iOS/MPUnlockViewController.h
+++ b/MasterPassword/iOS/MPUnlockViewController.h
@@ -18,6 +18,8 @@
@property (weak, nonatomic) IBOutlet UILabel *oldNameLabel;
@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate;
@property (weak, nonatomic) IBOutlet UILabel *deleteTip;
+@property (weak, nonatomic) IBOutlet UIView *passwordTipView;
+@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel;
@property(nonatomic, strong) UIColor *avatarShadowColor;
diff --git a/MasterPassword/iOS/MPUnlockViewController.m b/MasterPassword/iOS/MPUnlockViewController.m
index ded748d8..36227759 100644
--- a/MasterPassword/iOS/MPUnlockViewController.m
+++ b/MasterPassword/iOS/MPUnlockViewController.m
@@ -30,6 +30,8 @@
@synthesize nameLabel, oldNameLabel;
@synthesize avatarTemplate;
@synthesize deleteTip;
+@synthesize passwordTipView;
+@synthesize passwordTipLabel;
@synthesize avatarShadowColor = _avatarShadowColor;
@@ -52,6 +54,7 @@
self.nameLabel.layer.cornerRadius = 5;
self.avatarTemplate.hidden = YES;
self.spinner.alpha = 0;
+ self.passwordTipView.alpha = 0;
[self updateLayoutAnimated:NO allowScroll:YES completion:nil];
@@ -67,6 +70,8 @@
[self setNameLabel:nil];
[self setAvatarTemplate:nil];
[self setDeleteTip:nil];
+ [self setPasswordTipView:nil];
+ [self setPasswordTipLabel:nil];
[super viewDidUnload];
}
@@ -93,22 +98,23 @@
}
- (void)updateUsers {
-
+
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil];
// Clean up avatars.
- for (UIView *view in [self.avatarsView subviews])
- if (view != self.avatarTemplate)
- [view removeFromSuperview];
+ for (UIView *subview in [self.avatarsView subviews])
+ if ([[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
+ // This subview is a former avatar.
+ [subview removeFromSuperview];
[self.avatarToUser removeAllObjects];
-
+
// Create avatars.
for (MPUserEntity *user in users)
[self setupAvatar:[self.avatarTemplate clone] forUser:user];
[self setupAvatar:[self.avatarTemplate clone] forUser:nil];
-
+
// Scroll view's content changed, update its content size.
[self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
@@ -147,11 +153,11 @@
avatar.backgroundColor = [UIColor clearColor];
dbg(@"User: %@, avatar: %d", user.name, user.avatar);
+ avatar.tag = user.avatar;
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)]
forState:UIControlStateNormal];
- if (user)
- [self.avatarToUser setObject:user forKey:[NSValue valueWithNonretainedObject:avatar]];
+ [self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]];
if (self.selectedUser && user == self.selectedUser)
avatar.selected = YES;
@@ -165,8 +171,9 @@
[self.passwordField resignFirstResponder];
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
- if (finished) if (self.selectedUser)
- [self.passwordField becomeFirstResponder];
+ if (finished)
+ if (self.selectedUser)
+ [self.passwordField becomeFirstResponder];
}];
}
@@ -197,7 +204,7 @@
}
- (void)updateLayoutAnimated:(BOOL)animated allowScroll:(BOOL)allowScroll completion:(void (^)(BOOL finished))completion {
-
+
if (animated) {
self.oldNameLabel.text = self.nameLabel.text;
self.oldNameLabel.alpha = 1;
@@ -232,7 +239,7 @@
self.oldNameLabel.center = self.nameLabel.center;
self.avatarShadowColor = [UIColor lightGrayColor];
}
-
+
MPUserEntity *targetedUser = self.selectedUser;
UIButton *selectedAvatar = [self avatarForUser:self.selectedUser];
UIButton *targetedAvatar = selectedAvatar;
@@ -242,20 +249,26 @@
}
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
- const BOOL isTargeted = subview == targetedAvatar;
+ if (![[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
+ // This subview is not one of the user avatars.
+ return;
+ UIButton *avatar = (UIButton *)subview;
+
+ BOOL isTargeted = avatar == targetedAvatar;
+
+ avatar.userInteractionEnabled = isTargeted;
+ avatar.alpha = isTargeted ? 1 : self.selectedUser ? 0.1 : 0.4;
- subview.userInteractionEnabled = isTargeted;
- subview.alpha = isTargeted ? 1 : self.selectedUser ? 0.1 : 0.4;
-
- [self updateAvatarShadowColor:subview isTargeted:isTargeted];
+ [self updateAvatarShadowColor:avatar isTargeted:isTargeted];
} recurse:NO];
if (allowScroll) {
- CGPoint targetContentOffset = CGPointMake(targetedAvatar.center.x - self.avatarsView.bounds.size.width / 2, self.avatarsView.contentOffset.y);
+ CGPoint targetContentOffset = CGPointMake(MAX(0, targetedAvatar.center.x - self.avatarsView.bounds.size.width / 2),
+ self.avatarsView.contentOffset.y);
if (!CGPointEqualToPoint(self.avatarsView.contentOffset, targetContentOffset))
[self.avatarsView setContentOffset:targetContentOffset animated:animated];
}
-
+
self.nameLabel.text = targetedUser ? targetedUser.name : @"New User";
self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds,
[self.nameLabel.text sizeWithFont:self.nameLabel.font
@@ -266,23 +279,30 @@
completion(YES);
}
+- (void)setPasswordTip:(NSString *)string {
+
+ if (string.length)
+ self.passwordTipLabel.text = string;
+
+ [UIView animateWithDuration:0.3f animations:^{
+ self.passwordTipView.alpha = string.length? 1: 0;
+ }];
+}
+
- (void)tryMasterPassword {
[self setSpinnerActive:YES];
- [self changeAvatarShadowColorTo:[UIColor colorWithName:@"lightskyblue"]];
-
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL unlocked = [[MPAppDelegate get] tryMasterPassword:self.passwordField.text forUser:self.selectedUser];
dispatch_async(dispatch_get_main_queue(), ^{
if (unlocked) {
- [self changeAvatarShadowColorTo:[UIColor colorWithName:@"greenyellow"]];
-
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long) (NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long) (NSEC_PER_SEC * 0.5f)), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
});
} else
- [self changeAvatarShadowColorTo:[UIColor colorWithName:@"crimson"]];
+ [self setPasswordTip:@"Incorrect password."];
[self setSpinnerActive:NO];
});
@@ -309,7 +329,7 @@
- (MPUserEntity *)userForAvatar:(UIButton *)avatar {
- return NullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]);
+ return NSNullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]);
}
- (void)setSpinnerActive:(BOOL)active {
@@ -342,23 +362,12 @@
});
}
-- (void)changeAvatarShadowColorTo:(UIColor *)color {
-
- self.avatarShadowColor = color;
-
- if (self.selectedUser) {
- UIButton *selectedAvatar = [self avatarForUser:self.selectedUser];
- [selectedAvatar.layer removeAnimationForKey:@"targetedShadow"];
- [self updateAvatarShadowColor:selectedAvatar isTargeted:YES];
- }
-}
-
-- (void)updateAvatarShadowColor:(UIView *)avatar isTargeted:(BOOL)targeted {
+- (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted {
if (targeted) {
if (![avatar.layer animationForKey:@"targetedShadow"]) {
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
- toShadowColorAnimation.toValue = (__bridge id) self.avatarShadowColor.CGColor;
+ toShadowColorAnimation.toValue = (__bridge id) (avatar.selected? self.avatarTemplate.backgroundColor: [UIColor whiteColor]).CGColor;
toShadowColorAnimation.beginTime = 0.0f;
toShadowColorAnimation.duration = 0.5f;
toShadowColorAnimation.fillMode = kCAFillModeForwards;
@@ -404,13 +413,17 @@
#pragma mark - UITextFieldDelegate
+- (void)textFieldDidBeginEditing:(UITextField *)textField {
+
+ [self setPasswordTip:nil];
+}
+
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
[self setSpinnerActive:YES];
- [self changeAvatarShadowColorTo:[UIColor colorWithName:@"lightskyblue"]];
-
+
if (self.selectedUser.keyID)
[self tryMasterPassword];
@@ -463,11 +476,7 @@
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
- // CGFloat xOfMiddle = scrollView.contentOffset.x + scrollView.bounds.size.width / 2;
- // UIButton *middleAvatar = (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, scrollView.contentOffset.y) ofArray:scrollView.subviews];
- //
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
- // [self scrollToAvatar:middleAvatar animated:NO];
}
#pragma mark - IBActions
diff --git a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard
index 3b9acd45..7cacd1cd 100644
--- a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard
+++ b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard
@@ -16,18 +16,18 @@
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -128,7 +155,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
+
@@ -148,7 +175,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
-
+
@@ -181,7 +208,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
+
@@ -194,7 +221,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
+
@@ -230,11 +257,11 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
-
+
+
-
+
@@ -248,7 +275,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
+
If you need to specify a custom password for your site, use one of these types. The site's password will be encrypted using your master password.
@@ -256,13 +283,25 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -270,8 +309,8 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
-
+
+
@@ -283,7 +322,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
+
@@ -324,9 +363,6 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
-
-
-
@@ -779,6 +815,25 @@ L4m3P4sSw0rD
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -786,12 +841,12 @@ L4m3P4sSw0rD
-
-
+
+
-