2
0

Compare commits

..

6 Commits

Author SHA1 Message Date
Maarten Billemont
d704f451a3 Fixed issue causing emergency generator password button to not respond. 2017-04-17 22:27:36 -04:00
Maarten Billemont
2c9ab5d153 Fixed issue when cancelling touchID login. 2017-04-17 22:13:01 -04:00
Maarten Billemont
d5d33da12f Fixed UI issues with passwords list and drop-down animation + support for phrase and name default types.
[FIXED]     Fixed issues with animating changes in the passwords list during certain & multiple events.
[FIXED]     Slightly broken UI prior to drop-down animation & improved animation a bit.
[ADDED]     Phrase & Name default password types.
2017-04-17 21:57:08 -04:00
Maarten Billemont
cbef1a611b Update Mac binary to 2.5-mac-2 2017-04-16 13:03:15 -04:00
Maarten Billemont
0a1f215a1a Style login name, add login generated gear, improve logic for when to show login name. 2017-04-15 10:57:52 -04:00
Maarten Billemont
907d2a8ca6 Fixed key disappearing from NSCache after suspension and not being reloaded from keychain. 2017-04-15 02:28:11 -04:00
18 changed files with 586 additions and 543 deletions

View File

@@ -430,58 +430,38 @@ NSOperationQueue *_mpwQueue = nil;
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create(); return PearlAwait( ^(void (^setResult)(id)) {
dispatch_group_enter( group ); [self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
__block NSString *result = nil; setResult( result_ );
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) { }];
result = result_; } );
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
} }
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create(); return PearlAwait( ^(void (^setResult)(id)) {
dispatch_group_enter( group ); [self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
__block NSString *result = nil; setResult( result_ );
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) { }];
result = result_; } );
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
} }
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create(); return PearlAwait( ^(void (^setResult)(id)) {
dispatch_group_enter( group ); [self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
__block NSString *result = nil; setResult( result_ );
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) { }];
result = result_; } );
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
} }
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey { - (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create(); return PearlAwait( ^(void (^setResult)(id)) {
dispatch_group_enter( group ); [self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
__block NSString *result = nil; setResult( result_ );
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) { }];
result = result_; } );
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
} }
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { - (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
@@ -498,10 +478,12 @@ NSOperationQueue *_mpwQueue = nil;
else else
algorithm = site.algorithm; algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ if (!loginGenerated || [loginName length])
resultBlock( loginName || !loginGenerated? loginName: resultBlock( loginName );
[algorithm generateLoginForSiteNamed:name usingKey:siteKey] ); else
} ); PearlNotMainQueue( ^{
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
} );
} }
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { - (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
@@ -533,9 +515,8 @@ NSOperationQueue *_mpwQueue = nil;
else else
algorithm = site.algorithm; algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ PearlNotMainQueue( ^{
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey]; resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] );
resultBlock( result );
} ); } );
break; break;
} }
@@ -549,9 +530,8 @@ NSOperationQueue *_mpwQueue = nil;
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject; NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ PearlNotMainQueue( ^{
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey]; resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
resultBlock( result );
} ); } );
break; break;
} }
@@ -563,9 +543,8 @@ NSOperationQueue *_mpwQueue = nil;
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name]; NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery]; NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ PearlNotMainQueue( ^{
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey]; resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
resultBlock( result );
} ); } );
break; break;
} }
@@ -584,9 +563,8 @@ NSOperationQueue *_mpwQueue = nil;
else else
algorithm = site.algorithm; algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ PearlNotMainQueue( ^{
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey]; resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] );
resultBlock( result );
} ); } );
} }
@@ -605,9 +583,8 @@ NSOperationQueue *_mpwQueue = nil;
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers]) else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
algorithm = question.site.algorithm; algorithm = question.site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ PearlNotMainQueue( ^{
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey]; resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] );
resultBlock( result );
} ); } );
} }

View File

@@ -72,14 +72,23 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
MPKeyOrigin keyOrigin; MPKeyOrigin keyOrigin;
NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin ); NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin );
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery]; id<MPAlgorithm> keyAlgorithm = user.algorithm;
if (!keyData) { MPKey *key = [[MPKey alloc] initForFullName:user.name withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
return ![algorithm isEqual:keyAlgorithm]? nil:
PearlMainQueueAwait( (id)^{
return [PearlKeyChain dataOfItemForQuery:keyQuery];
} );
} keyOrigin:keyOrigin];
if ([key keyIDForAlgorithm:user.algorithm])
inf( @"Found key in keychain for user: %@", user.userID );
else {
inf( @"No key found in keychain for user: %@", user.userID ); inf( @"No key found in keychain for user: %@", user.userID );
return nil; key = nil;
} }
inf( @"Found key in keychain for user: %@", user.userID ); return key;
return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm keyOrigin:keyOrigin];
} }
- (void)storeSavedKeyFor:(MPUserEntity *)user { - (void)storeSavedKeyFor:(MPUserEntity *)user {
@@ -230,28 +239,22 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
NSString *content; NSString *content;
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) { while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use. // Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
__block NSString *masterPassword = nil; NSString *masterPassword = nil;
#ifdef PEARL_UIKIT #ifdef PEARL_UIKIT
dispatch_group_t recoverPasswordGroup = dispatch_group_create(); masterPassword = PearlAwait( ^(void (^setResult)(id)) {
dispatch_group_enter( recoverPasswordGroup ); [PearlAlert showAlertWithTitle:@"Enter Old Master Password"
[PearlAlert showAlertWithTitle:@"Enter Old Master Password" message:PearlString(
message:PearlString( @"Your old master password is required to migrate the stored password for %@", @"Your old master password is required to migrate the stored password for %@",
site.name ) site.name )
viewStyle:UIAlertViewStyleSecureTextInput viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex]) if (buttonIndex_ == [alert_ cancelButtonIndex])
// Don't Migrate setResult( nil );
return; else
setResult( [alert_ textFieldAtIndex:0].text );
masterPassword = [alert_ textFieldAtIndex:0].text; } cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
} } );
@finally {
dispatch_group_leave( recoverPasswordGroup );
}
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
dispatch_group_wait( recoverPasswordGroup, DISPATCH_TIME_FOREVER );
#endif #endif
if (!masterPassword) if (!masterPassword)
// Don't Migrate // Don't Migrate

View File

@@ -28,12 +28,12 @@ typedef NS_ENUM( NSUInteger, MPKeyOrigin ) {
@interface MPKey : NSObject @interface MPKey : NSObject
@property(nonatomic, readonly) NSString *fullName;
@property(nonatomic, readonly) MPKeyOrigin origin; @property(nonatomic, readonly) MPKeyOrigin origin;
@property(nonatomic, readonly, copy) NSString *fullName;
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword; - (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData - (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin; keyOrigin:(MPKeyOrigin)origin;
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm; - (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm; - (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm;

View File

@@ -19,9 +19,9 @@
@interface MPKey() @interface MPKey()
@property(nonatomic) NSString *fullName;
@property(nonatomic) MPKeyOrigin origin; @property(nonatomic) MPKeyOrigin origin;
@property(nonatomic) NSString *masterPassword; @property(nonatomic, copy) NSString *fullName;
@property(nonatomic, copy) NSData *( ^keyResolver )(id<MPAlgorithm>);
@end @end
@@ -31,25 +31,22 @@
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword { - (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
return [self initForFullName:fullName withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
return [algorithm keyDataForFullName:self.fullName withMasterPassword:masterPassword];
} keyOrigin:MPKeyOriginMasterPassword];
}
- (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
keyOrigin:(MPKeyOrigin)origin {
if (!(self = [super init])) if (!(self = [super init]))
return nil; return nil;
_keyCache = [NSCache new]; _keyCache = [NSCache new];
self.fullName = fullName;
self.origin = MPKeyOriginMasterPassword;
self.masterPassword = masterPassword;
return self;
}
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin {
if (!(self = [self initForFullName:fullName withMasterPassword:nil]))
return nil;
self.origin = origin; self.origin = origin;
[_keyCache setObject:keyData forKey:algorithm]; self.fullName = fullName;
self.keyResolver = keyResolver;
return self; return self;
} }
@@ -61,15 +58,17 @@
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm { - (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm {
NSData *keyData = [_keyCache objectForKey:algorithm]; @synchronized (self) {
if (keyData) NSData *keyData = [_keyCache objectForKey:algorithm];
if (keyData)
return keyData;
keyData = self.keyResolver( algorithm );
if (keyData)
[_keyCache setObject:keyData forKey:algorithm];
return keyData; return keyData;
}
keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword];
if (keyData)
[_keyCache setObject:keyData forKey:algorithm];
return keyData;
} }
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength { - (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
@@ -80,7 +79,7 @@
- (BOOL)isEqualToKey:(MPKey *)key { - (BOOL)isEqualToKey:(MPKey *)key {
return [self.fullName isEqualToString:key.fullName] && [self.masterPassword isEqualToString:self.masterPassword]; return [[self keyIDForAlgorithm:MPAlgorithmDefault] isEqualToData:[key keyIDForAlgorithm:MPAlgorithmDefault]];
} }
- (BOOL)isEqual:(id)object { - (BOOL)isEqual:(id)object {

View File

@@ -28,10 +28,10 @@
[super windowDidLoad]; [super windowDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, nil, ^(id host, NSNotification *note) {
queue:nil usingBlock:^(NSNotification *note) { PearlRemoveNotificationObserversFrom( host );
[MPMacAppDelegate get].initialWindowController = nil; [MPMacAppDelegate get].initialWindowController = nil;
}]; } );
} }
#pragma mark - Actions #pragma mark - Actions

View File

@@ -41,40 +41,38 @@
[self replaceFonts:self.window.contentView]; [self replaceFonts:self.window.contentView];
prof_rewind( @"replaceFonts" ); prof_rewind( @"replaceFonts" );
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window PearlAddNotificationObserver( NSWindowDidBecomeKeyNotification, self.window, [NSOperationQueue mainQueue],
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { ^(id host, NSNotification *note) {
prof_new( @"didBecomeKey" ); prof_new( @"didBecomeKey" );
[self.window makeKeyAndOrderFront:nil]; [self.window makeKeyAndOrderFront:nil];
prof_rewind( @"fadeIn" ); prof_rewind( @"fadeIn" );
[self updateUser]; [self updateUser];
prof_finish( @"updateUser" ); prof_finish( @"updateUser" );
}]; } );
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, [NSOperationQueue mainQueue],
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { ^(id host, NSNotification *note) {
PearlRemoveNotificationObservers();
NSWindow *sheet = [self.window attachedSheet]; NSWindow *sheet = [self.window attachedSheet];
if (sheet) if (sheet)
[self.window endSheet:sheet]; [self.window endSheet:sheet];
}]; } );
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { ^(id host, NSNotification *note) {
[self.window close]; [self.window close];
}]; } );
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil PearlAddNotificationObserver( MPSignedInNotification, nil, [NSOperationQueue mainQueue], ^(id host, NSNotification *note) {
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { [self updateUser];
[self updateUser]; } );
}]; PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue], ^(id host, NSNotification *note) {
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil [self updateUser];
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { } );
[self updateUser];
}];
[self observeKeyPath:@"sitesController.selection" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) { [self observeKeyPath:@"sitesController.selection" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
prof_new( @"sitesController.selection" );
[_self updateSelection]; [_self updateSelection];
prof_finish( @"updateSelection" );
}]; }];
prof_rewind( @"observers" ); prof_rewind( @"observers" );
NSSearchFieldCell *siteFieldCell = (NSSearchFieldCell *)self.siteField.cell; NSSearchFieldCell *siteFieldCell = self.siteField.cell;
siteFieldCell.searchButtonCell = nil; siteFieldCell.searchButtonCell = nil;
siteFieldCell.cancelButtonCell = nil; siteFieldCell.cancelButtonCell = nil;

View File

@@ -28,7 +28,7 @@
@property(weak, nonatomic) IBOutlet UISegmentedControl *typeControl; @property(weak, nonatomic) IBOutlet UISegmentedControl *typeControl;
@property(weak, nonatomic) IBOutlet UILabel *counterLabel; @property(weak, nonatomic) IBOutlet UILabel *counterLabel;
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *activity; @property(weak, nonatomic) IBOutlet UIActivityIndicatorView *activity;
@property(weak, nonatomic) IBOutlet UILabel *passwordLabel; @property(weak, nonatomic) IBOutlet UIButton *passwordButton;
@property(weak, nonatomic) IBOutlet UIView *tipContainer; @property(weak, nonatomic) IBOutlet UIView *tipContainer;
- (IBAction)controlChanged:(UIControl *)control; - (IBAction)controlChanged:(UIControl *)control;

View File

@@ -81,21 +81,19 @@
[self updatePassword]; [self updatePassword];
} }
- (IBAction)copyPassword:(UITapGestureRecognizer *)recognizer { - (IBAction)copyPassword:(id)sender {
if (recognizer.state == UIGestureRecognizerStateEnded) { NSString *sitePassword = [self.passwordButton titleForState:UIControlStateNormal];
NSString *sitePassword = self.passwordLabel.text; if ([sitePassword length]) {
if ([sitePassword length]) { [UIPasteboard generalPasteboard].string = sitePassword;
[UIPasteboard generalPasteboard].string = sitePassword; [UIView animateWithDuration:0.3f animations:^{
[UIView animateWithDuration:0.3f animations:^{ self.tipContainer.alpha = 1;
self.tipContainer.alpha = 1; } completion:^(BOOL finished) {
} completion:^(BOOL finished) { if (finished)
if (finished) PearlMainQueueAfter( 3, ^{
PearlMainQueueAfter( 3, ^{ self.tipContainer.alpha = 0;
self.tipContainer.alpha = 0; } );
} ); }];
}];
}
} }
} }
@@ -106,7 +104,7 @@
NSString *fullName = self.fullNameField.text; NSString *fullName = self.fullNameField.text;
NSString *masterPassword = self.masterPasswordField.text; NSString *masterPassword = self.masterPasswordField.text;
self.passwordLabel.text = nil; [self.passwordButton setTitle:nil forState:UIControlStateNormal];
[self.activity startAnimating]; [self.activity startAnimating];
[_emergencyKeyQueue cancelAllOperations]; [_emergencyKeyQueue cancelAllOperations];
[_emergencyKeyQueue addOperationWithBlock:^{ [_emergencyKeyQueue addOperationWithBlock:^{
@@ -128,7 +126,7 @@
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value; NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter ); self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
self.passwordLabel.text = nil; [self.passwordButton setTitle:nil forState:UIControlStateNormal];
[self.activity startAnimating]; [self.activity startAnimating];
[_emergencyPasswordQueue cancelAllOperations]; [_emergencyPasswordQueue cancelAllOperations];
[_emergencyPasswordQueue addOperationWithBlock:^{ [_emergencyPasswordQueue addOperationWithBlock:^{
@@ -138,7 +136,7 @@
PearlMainQueue( ^{ PearlMainQueue( ^{
[self.activity stopAnimating]; [self.activity stopAnimating];
self.passwordLabel.text = sitePassword; [self.passwordButton setTitle:sitePassword forState:UIControlStateNormal];
} ); } );
}]; }];
} }

View File

@@ -28,6 +28,7 @@
@property(nonatomic, strong) IBOutlet UITextField *passwordField; @property(nonatomic, strong) IBOutlet UITextField *passwordField;
@property(nonatomic, strong) IBOutlet UIView *loginNameContainer; @property(nonatomic, strong) IBOutlet UIView *loginNameContainer;
@property(nonatomic, strong) IBOutlet UITextField *loginNameField; @property(nonatomic, strong) IBOutlet UITextField *loginNameField;
@property(nonatomic, strong) IBOutlet UILabel *loginNameGenerated;
@property(nonatomic, strong) IBOutlet UILabel *strengthLabel; @property(nonatomic, strong) IBOutlet UILabel *strengthLabel;
@property(nonatomic, strong) IBOutlet UILabel *counterLabel; @property(nonatomic, strong) IBOutlet UILabel *counterLabel;
@property(nonatomic, strong) IBOutlet UIButton *counterButton; @property(nonatomic, strong) IBOutlet UIButton *counterButton;
@@ -199,7 +200,7 @@
atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES]; atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
if (textField == self.loginNameField) if (textField == self.loginNameField)
self.loginNameButton.titleLabel.alpha = [self.loginNameField.text length] || self.loginNameField.enabled? 0: 1; self.loginNameButton.hidden = [self.loginNameField.attributedText length] || self.loginNameField.enabled;
} }
- (IBAction)textFieldDidChange:(UITextField *)textField { - (IBAction)textFieldDidChange:(UITextField *)textField {
@@ -224,7 +225,7 @@
if (textField == self.passwordField || textField == self.loginNameField) { if (textField == self.passwordField || textField == self.loginNameField) {
textField.enabled = NO; textField.enabled = NO;
NSString *text = textField.text; NSString *text = [textField.attributedText string]?: textField.text;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *site = [self siteInContext:context]; MPSiteEntity *site = [self siteInContext:context];
@@ -235,10 +236,8 @@
if ([site.algorithm savePassword:text toSite:site usingKey:[MPiOSAppDelegate get].key]) if ([site.algorithm savePassword:text toSite:site usingKey:[MPiOSAppDelegate get].key])
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2]; [PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
} }
else if (textField == self.loginNameField && else if (textField == self.loginNameField) {
((site.loginGenerated && ![text length]) || if (![text isEqualToString:[site.algorithm resolveLoginForSite:site usingKey:[MPiOSAppDelegate get].key]]) {
(!site.loginGenerated && ![text isEqualToString:site.loginName]))) {
if (site.loginGenerated || !([site.loginName isEqualToString:text] || (!text && !site.loginName))) {
site.loginGenerated = NO; site.loginGenerated = NO;
site.loginName = text; site.loginName = text;
@@ -508,7 +507,6 @@
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers]; self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
BOOL settingsMode = self.mode == MPPasswordCellModeSettings; BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0; self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0;
self.loginNameField.textColor = [UIColor colorWithHexString:mainSite.loginGenerated? @"5E636D": @"6D5E63"];
self.modeButton.alpha = self.transientSite? 0: settingsMode? 0.5f: 0.1f; self.modeButton.alpha = self.transientSite? 0: settingsMode? 0.5f: 0.1f;
self.counterLabel.alpha = self.counterButton.alpha = mainSite.type & MPSiteTypeClassGenerated? 0.5f: 0; self.counterLabel.alpha = self.counterButton.alpha = mainSite.type & MPSiteTypeClassGenerated? 0.5f: 0;
self.modeButton.selected = settingsMode; self.modeButton.selected = settingsMode;
@@ -520,13 +518,21 @@
[self.passwordField resignFirstResponder]; [self.passwordField resignFirstResponder];
} }
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins]) if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
[self.loginNameButton setTitle:@"Tap to generate username or use pencil to save one" forState:UIControlStateNormal]; [self.loginNameButton setTitle:@"Tap here to ⚙ generate username or the pencil to type one" forState:UIControlStateNormal];
else else
[self.loginNameButton setTitle:@"Tap the pencil to save a username" forState:UIControlStateNormal]; [self.loginNameButton setTitle:@"Tap the pencil to type a username" forState:UIControlStateNormal];
// Site Name // Site Name
[self updateSiteName:mainSite]; [self updateSiteName:mainSite];
// Site Counter
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
// Site Login Name
self.loginNameField.enabled = self.passwordField.enabled = //
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
// Site Password // Site Password
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue]; self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
self.passwordField.attributedPlaceholder = stra( self.passwordField.attributedPlaceholder = stra(
@@ -534,12 +540,15 @@
mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{ mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{
NSForegroundColorAttributeName: [UIColor whiteColor] NSForegroundColorAttributeName: [UIColor whiteColor]
} ); } );
// Calculate Fields
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *site = [self siteInContext:context]; MPSiteEntity *site = [self siteInContext:context];
MPKey *key = [MPiOSAppDelegate get].key; MPKey *key = [MPiOSAppDelegate get].key;
if (!key) if (!key)
return; return;
BOOL loginGenerated = site.loginGenerated;
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key]; NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPSiteTypeGeneratedLong; MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPSiteTypeGeneratedLong;
if (self.transientSite && transientType & MPSiteTypeClassGenerated) if (self.transientSite && transientType & MPSiteTypeClassGenerated)
@@ -559,13 +568,15 @@
BOOL requiresExplicitMigration = site.requiresExplicitMigration; BOOL requiresExplicitMigration = site.requiresExplicitMigration;
PearlMainQueue( ^{ PearlMainQueue( ^{
self.loginNameField.text = loginName;
self.passwordField.text = password; self.passwordField.text = password;
self.strengthLabel.text = timeToCrackString; self.strengthLabel.text = timeToCrackString;
self.loginNameButton.titleLabel.alpha = [loginName length] || self.loginNameField.enabled? 0: 1; self.loginNameGenerated.hidden = !loginGenerated;
self.loginNameField.attributedText =
strarm( stra( loginName?: @"", self.siteNameLabel.textAttributes ), NSParagraphStyleAttributeName, nil );
self.loginNameButton.hidden = [loginName length] || self.loginNameField.enabled;
if (![password length]) { if (![password length]) {
self.indicatorView.alpha = 1; self.indicatorView.hidden = NO;
[self.indicatorView removeFromSuperview]; [self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView]; [self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX [self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
@@ -575,7 +586,7 @@
}]; }];
} }
else if (requiresExplicitMigration) { else if (requiresExplicitMigration) {
self.indicatorView.alpha = 1; self.indicatorView.hidden = NO;
[self.indicatorView removeFromSuperview]; [self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView]; [self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX [self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
@@ -585,18 +596,10 @@
}]; }];
} }
else else
self.indicatorView.alpha = 0; self.indicatorView.hidden = YES;
} ); } );
}]; }];
// Site Counter
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
// Site Login Name
self.loginNameField.enabled = self.passwordField.enabled = //
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
[self.contentView layoutIfNeeded]; [self.contentView layoutIfNeeded];
}]; }];
} }
@@ -616,8 +619,9 @@
range:NSMakeRange( s, [self.fuzzyGroups[f] length] )]; range:NSMakeRange( s, [self.fuzzyGroups[f] length] )];
} }
[attributedSiteName appendAttributedString:stra( if (self.transientSite)
strf( @" - %@", self.transientSite? @"Tap to create": [site.algorithm shortNameOfType:site.type] ), @{} )]; [attributedSiteName appendAttributedString:stra( @" Tap to create", @{} )];
self.siteNameLabel.attributedText = attributedSiteName; self.siteNameLabel.attributedText = attributedSiteName;
} }

View File

@@ -46,6 +46,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
NSUInteger _transientItem; NSUInteger _transientItem;
NSCharacterSet *_siteNameAcceptableCharactersSet; NSCharacterSet *_siteNameAcceptableCharactersSet;
NSArray *_fuzzyGroups; NSArray *_fuzzyGroups;
NSMutableArray *_passwordCollectionViewUpdatesBatch;
} }
#pragma mark - Life #pragma mark - Life
@@ -62,6 +63,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
_backgroundColor = self.passwordCollectionView.backgroundColor; _backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f]; _darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_transientItem = NSNotFound; _transientItem = NSNotFound;
_passwordCollectionViewUpdatesBatch = [NSMutableArray arrayWithCapacity:4];
self.view.backgroundColor = [UIColor clearColor]; self.view.backgroundColor = [UIColor clearColor];
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard]; [self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
@@ -181,10 +183,13 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
Weakify( self );
if (controller == _fetchedResultsController) { if (controller == _fetchedResultsController) {
@try { @synchronized (_passwordCollectionViewUpdatesBatch) {
[self.passwordCollectionView performBatchUpdates:^{ [_passwordCollectionViewUpdatesBatch addObject:[^{
[self fetchedItemsDidUpdate]; Strongify( self );
switch (type) { switch (type) {
case NSFetchedResultsChangeInsert: case NSFetchedResultsChangeInsert:
[self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]]; [self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
@@ -193,18 +198,35 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]]; [self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]];
break; break;
case NSFetchedResultsChangeMove: case NSFetchedResultsChangeMove:
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; if (![indexPath isEqual:newIndexPath])
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
break; break;
case NSFetchedResultsChangeUpdate: case NSFetchedResultsChangeUpdate:
[self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]]; [self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]];
break; break;
} }
} completion:nil]; } copy]];
}
@catch (NSException *exception) {
wrn( @"While updating password cells: %@", [exception fullDescription] );
[self.passwordCollectionView reloadData];
} }
[controller.managedObjectContext performBlock:^{
PearlMainQueueOperation( ^{
@try {
[self.passwordCollectionView performBatchUpdates:^{
[self updateTransientItem];
@synchronized (_passwordCollectionViewUpdatesBatch) {
for (VoidBlock block in _passwordCollectionViewUpdatesBatch)
block();
[_passwordCollectionViewUpdatesBatch removeAllObjects];
}
} completion:nil];
}
@catch (NSException *exception) {
wrn( @"While updating password cells: %@", [exception fullDescription] );
[self.passwordCollectionView reloadData];
}
} );
}];
} }
} }
@@ -290,25 +312,24 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
}]; }];
} }
- (void)fetchedItemsDidUpdate { - (void)updateTransientItem {
NSString *query = self.query; NSString *query = self.query;
_showTransientItem = [query length] > 0; _showTransientItem = [query length] > 0 && ![[[self.fetchedResultsController.sections[0] objects] filteredArrayUsingPredicate:
NSUInteger objects = ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[0]).numberOfObjects; [NSPredicate predicateWithBlock:^BOOL(MPSiteEntity *site, NSDictionary<NSString *, id> *bindings) {
if (_showTransientItem && objects == 1 && return [site.name isEqualToString:query];
[[[self.fetchedResultsController.fetchedObjects firstObject] name] isEqualToString:query]) }]] count];
_showTransientItem = NO; if (!_showTransientItem && _transientItem != NSNotFound)
if ([self.passwordCollectionView numberOfSections] > 0) { [self.passwordCollectionView deleteItemsAtIndexPaths:
if (!_showTransientItem && _transientItem != NSNotFound) @[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
[self.passwordCollectionView deleteItemsAtIndexPaths: else if (_showTransientItem && _transientItem == NSNotFound) {
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]]; NSUInteger objects = [self.fetchedResultsController.sections[0] numberOfObjects];
else if (_showTransientItem && _transientItem == NSNotFound) [self.passwordCollectionView insertItemsAtIndexPaths:
[self.passwordCollectionView insertItemsAtIndexPaths: @[ [NSIndexPath indexPathForItem:objects inSection:0] ]];
@[ [NSIndexPath indexPathForItem:objects inSection:0] ]];
else if (_transientItem != NSNotFound)
[self.passwordCollectionView reloadItemsAtIndexPaths:
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
} }
else if (_transientItem != NSNotFound)
[self.passwordCollectionView reloadItemsAtIndexPaths:
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
} }
- (void)registerObservers { - (void)registerObservers {
@@ -361,6 +382,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
if (mainContext) if (mainContext)
PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil, PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil,
^(MPPasswordsViewController *self, NSNotification *note) { ^(MPPasswordsViewController *self, NSNotification *note) {
// TODO: either move this into the app delegate or remove the duplicate signOutAnimated: call from the app delegate.
if (![[MPiOSAppDelegate get] activeUserInContext:note.object]) if (![[MPiOSAppDelegate get] activeUserInContext:note.object])
[[MPiOSAppDelegate get] signOutAnimated:YES]; [[MPiOSAppDelegate get] signOutAnimated:YES];
} ); } );
@@ -421,7 +443,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
PearlMainQueue( ^{ PearlMainQueue( ^{
@try { @try {
[self.passwordCollectionView performBatchUpdates:^{ [self.passwordCollectionView performBatchUpdates:^{
[self fetchedItemsDidUpdate]; [self updateTransientItem];
NSInteger fromSections = self.passwordCollectionView.numberOfSections; NSInteger fromSections = self.passwordCollectionView.numberOfSections;
NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView]; NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView];

View File

@@ -22,8 +22,6 @@
@implementation MPPopdownSegue { @implementation MPPopdownSegue {
} }
static char UnwindingObserverKey;
- (void)perform { - (void)perform {
MPPasswordsViewController *passwordsVC; MPPasswordsViewController *passwordsVC;
@@ -39,19 +37,20 @@ static char UnwindingObserverKey;
[passwordsVC.popdownContainer addConstraintsWithVisualFormats:@[ @"H:|[popdownView]|", @"V:|[popdownView]|" ] options:0 [passwordsVC.popdownContainer addConstraintsWithVisualFormats:@[ @"H:|[popdownView]|", @"V:|[popdownView]|" ] options:0
metrics:nil views:NSDictionaryOfVariableBindings( popdownView )]; metrics:nil views:NSDictionaryOfVariableBindings( popdownView )];
[UIView animateWithDuration:0.3f animations:^{ [passwordsVC.popdownToTopConstraint layoutIfNeeded];
[[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
} completion:^(BOOL finished) {
[popdownVC didMoveToParentViewController:passwordsVC];
id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil [UIView animateWithDuration:0.6f delay:0 usingSpringWithDamping:0.75f initialSpringVelocity:1
queue:[NSOperationQueue mainQueue] usingBlock: options:UIViewAnimationOptionCurveEaseOut animations:^{
^(NSNotification *note) { [[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:popdownVC } completion:^(BOOL finished) {
destination:passwordsVC] perform]; [popdownVC didMoveToParentViewController:passwordsVC];
}];
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, observer, OBJC_ASSOCIATION_RETAIN ); PearlAddNotificationObserverTo( popdownVC, MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
}]; ^(id host, NSNotification *note) {
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:popdownVC destination:passwordsVC]
perform];
} );
}];
} }
else { else {
popdownVC = self.sourceViewController; popdownVC = self.sourceViewController;
@@ -59,16 +58,16 @@ static char UnwindingObserverKey;
passwordsVC = (id)passwordsVC.parentViewController); passwordsVC = (id)passwordsVC.parentViewController);
NSAssert( passwordsVC, @"Couldn't find passwords VC to pop back to." ); NSAssert( passwordsVC, @"Couldn't find passwords VC to pop back to." );
[[NSNotificationCenter defaultCenter] removeObserver:objc_getAssociatedObject( popdownVC, &UnwindingObserverKey )]; PearlRemoveNotificationObserversFrom( popdownVC );
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, nil, OBJC_ASSOCIATION_RETAIN );
[popdownVC willMoveToParentViewController:nil]; [popdownVC willMoveToParentViewController:nil];
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{ [UIView animateWithDuration:0.4f delay:0 options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionOverrideInheritedDuration
[[passwordsVC.popdownToTopConstraint updatePriority:UILayoutPriorityDefaultHigh] layoutIfNeeded]; animations:^{
} completion:^(BOOL finished) { [[passwordsVC.popdownToTopConstraint updatePriority:UILayoutPriorityDefaultHigh] layoutIfNeeded];
[popdownVC.view removeFromSuperview]; } completion:^(BOOL finished) {
[popdownVC removeFromParentViewController]; [popdownVC.view removeFromSuperview];
}]; [popdownVC removeFromParentViewController];
}];
} }
} }

View File

@@ -29,8 +29,10 @@
@property(weak, nonatomic) IBOutlet UITableViewCell *exportCell; @property(weak, nonatomic) IBOutlet UITableViewCell *exportCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *checkInconsistencies; @property(weak, nonatomic) IBOutlet UITableViewCell *checkInconsistencies;
@property(weak, nonatomic) IBOutlet UIImageView *avatarImage; @property(weak, nonatomic) IBOutlet UIImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UISegmentedControl *generatedTypeControl; @property(weak, nonatomic) IBOutlet UISegmentedControl *generated1TypeControl;
@property(weak, nonatomic) IBOutlet UISegmentedControl *generated2TypeControl;
@property(weak, nonatomic) IBOutlet UISegmentedControl *storedTypeControl; @property(weak, nonatomic) IBOutlet UISegmentedControl *storedTypeControl;
@property(weak, nonatomic) IBOutlet UILabel *passwordTypeExample;
- (IBAction)previousAvatar:(id)sender; - (IBAction)previousAvatar:(id)sender;
- (IBAction)nextAvatar:(id)sender; - (IBAction)nextAvatar:(id)sender;

View File

@@ -55,12 +55,24 @@
- (void)reload { - (void)reload {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread]; MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:activeUser.defaultType];
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:activeUser.defaultType];
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%lu", (unsigned long)activeUser.avatar )]; self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%lu", (unsigned long)activeUser.avatar )];
self.savePasswordSwitch.on = activeUser.saveKey; self.savePasswordSwitch.on = activeUser.saveKey;
self.touchIDSwitch.on = activeUser.touchID; self.touchIDSwitch.on = activeUser.touchID;
self.touchIDSwitch.enabled = self.savePasswordSwitch.on && [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductTouchID]; self.touchIDSwitch.enabled = self.savePasswordSwitch.on && [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductTouchID];
MPSiteType defaultType = activeUser.defaultType;
self.generated1TypeControl.selectedSegmentIndex = [self generated1SegmentIndexForType:defaultType];
self.generated2TypeControl.selectedSegmentIndex = [self generated2SegmentIndexForType:defaultType];
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType];
PearlNotMainQueue( ^{
NSString *examplePassword = nil;
if (defaultType & MPSiteTypeClassGenerated)
examplePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:@"test" ofType:defaultType
withCounter:1 usingKey:[MPiOSAppDelegate get].key];
PearlMainQueue( ^{
self.passwordTypeExample.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil;
} );
} );
} }
#pragma mark - UITableViewDelegate #pragma mark - UITableViewDelegate
@@ -88,14 +100,18 @@
[self dismissPopup]; [self dismissPopup];
[[MPiOSAppDelegate get] signOutAnimated:YES]; [[MPiOSAppDelegate get] signOutAnimated:YES];
} }
if (cell == self.feedbackCell) if (cell == self.feedbackCell)
[[MPiOSAppDelegate get] showFeedbackWithLogs:YES forVC:self]; [[MPiOSAppDelegate get] showFeedbackWithLogs:YES forVC:self];
if (cell == self.exportCell) if (cell == self.exportCell)
[[MPiOSAppDelegate get] showExportForVC:self]; [[MPiOSAppDelegate get] showExportForVC:self];
if (cell == self.showHelpCell) { if (cell == self.showHelpCell) {
MPPasswordsViewController *passwordsVC = [self dismissPopup]; MPPasswordsViewController *passwordsVC = [self dismissPopup];
[passwordsVC performSegueWithIdentifier:@"guide" sender:self]; [passwordsVC performSegueWithIdentifier:@"guide" sender:self];
} }
if (cell == self.checkInconsistencies) if (cell == self.checkInconsistencies)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
if ([[MPiOSAppDelegate get] findAndFixInconsistenciesSaveInContext:context] == MPFixableResultNoProblems) if ([[MPiOSAppDelegate get] findAndFixInconsistenciesSaveInContext:context] == MPFixableResultNoProblems)
@@ -140,11 +156,13 @@
} ); } );
}]; }];
if (sender == self.generatedTypeControl || sender == self.storedTypeControl) { if (sender == self.generated1TypeControl || sender == self.generated2TypeControl || sender == self.storedTypeControl) {
if (sender == self.generatedTypeControl) if (sender != self.generated1TypeControl)
self.generated1TypeControl.selectedSegmentIndex = -1;
if (sender != self.generated2TypeControl)
self.generated2TypeControl.selectedSegmentIndex = -1;
if (sender != self.storedTypeControl)
self.storedTypeControl.selectedSegmentIndex = -1; self.storedTypeControl.selectedSegmentIndex = -1;
else if (sender == self.storedTypeControl)
self.generatedTypeControl.selectedSegmentIndex = -1;
MPSiteType defaultType = [self typeForSelectedSegment]; MPSiteType defaultType = [self typeForSelectedSegment];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
@@ -226,10 +244,17 @@
- (MPSiteType)typeForSelectedSegment { - (MPSiteType)typeForSelectedSegment {
NSInteger selectedGeneratedIndex = self.generatedTypeControl.selectedSegmentIndex; NSInteger selectedGenerated1Index = self.generated1TypeControl.selectedSegmentIndex;
NSInteger selectedGenerated2Index = self.generated2TypeControl.selectedSegmentIndex;
NSInteger selectedStoredIndex = self.storedTypeControl.selectedSegmentIndex; NSInteger selectedStoredIndex = self.storedTypeControl.selectedSegmentIndex;
switch (selectedGeneratedIndex) { switch (selectedGenerated1Index) {
case 0:
return MPSiteTypeGeneratedPhrase;
case 1:
return MPSiteTypeGeneratedName;
default:
switch (selectedGenerated2Index) {
case 0: case 0:
return MPSiteTypeGeneratedMaximum; return MPSiteTypeGeneratedMaximum;
case 1: case 1:
@@ -250,13 +275,26 @@
case 1: case 1:
return MPSiteTypeStoredDevicePrivate; return MPSiteTypeStoredDevicePrivate;
default: default:
Throw( @"unsupported selected type index: generated=%ld, stored=%ld", (long)selectedGeneratedIndex, Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld",
(long)selectedStoredIndex ); (long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex );
} }
} }
}
} }
- (NSInteger)generatedSegmentIndexForType:(MPSiteType)type { - (NSInteger)generated1SegmentIndexForType:(MPSiteType)type {
switch (type) {
case MPSiteTypeGeneratedPhrase:
return 0;
case MPSiteTypeGeneratedName:
return 1;
default:
return -1;
}
}
- (NSInteger)generated2SegmentIndexForType:(MPSiteType)type {
switch (type) { switch (type) {
case MPSiteTypeGeneratedMaximum: case MPSiteTypeGeneratedMaximum:

View File

@@ -89,11 +89,11 @@
if ([selectedSite isKindOfClass:[MPGeneratedSiteEntity class]]) if ([selectedSite isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)selectedSite).counter; counter = ((MPGeneratedSiteEntity *)selectedSite).counter;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{ PearlNotMainQueue( ^{
NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType
withCounter:counter usingKey:[MPiOSAppDelegate get].key]; withCounter:counter usingKey:[MPiOSAppDelegate get].key];
dispatch_async( dispatch_get_main_queue(), ^{ PearlMainQueue( ^{
[(UITextField *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:2] setText:typeContent]; [(UITextField *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:2] setText:typeContent];
} ); } );
} ); } );

View File

@@ -19,7 +19,6 @@
#import "MPiOSAppDelegate.h" #import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "IASKSettingsReader.h"
#import "MPStoreViewController.h" #import "MPStoreViewController.h"
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate> @interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@@ -164,26 +163,24 @@
return NO; return NO;
// Arbitrary URL to mpsites data. // Arbitrary URL to mpsites data.
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
NSError *error; ^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
NSURLResponse *response; if (error)
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
returningResponse:&response error:&error]; if (!importedSitesData) {
if (error) [PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@",
err( @"While reading imported sites from %@: %@", url, [error fullDescription] ); [error localizedDescription]?: error )];
if (!importedSitesData) { return;
[PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@", [error localizedDescription]?: error )]; }
return;
}
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
if (!importedSitesString) { if (!importedSitesString) {
[PearlAlert showError:@"Master Password couldn't understand the import file."]; [PearlAlert showError:@"Master Password couldn't understand the import file."];
return; return;
} }
[self importSites:importedSitesString]; [self importSites:importedSitesString];
} ); }] resume];
return YES; return YES;
} }
@@ -199,57 +196,33 @@
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"]; PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) { MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil; return PearlAwait( ^(void (^setResult)(id)) {
dispatch_group_t importPasswordGroup = dispatch_group_create();
dispatch_group_enter( importPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import File's Master Password" [PearlAlert showAlertWithTitle:@"Import File's Master Password"
message:strf( @"%@'s export was done using a different master password.\n" message:strf( @"%@'s export was done using a different master password.\n"
@"Enter that master password to unlock the exported data.", userName ) @"Enter that master password to unlock the exported data.", userName )
viewStyle:UIAlertViewStyleSecureTextInput viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try { if (buttonIndex_ == [alert_ cancelButtonIndex])
if (buttonIndex_ == [alert_ cancelButtonIndex]) setResult( nil );
return; else
setResult( [alert_ textFieldAtIndex:0].text );
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( importPasswordGroup );
}
} }
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil]; cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
} ); } );
dispatch_group_wait( importPasswordGroup, DISPATCH_TIME_FOREVER );
return masterPassword;
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) { } askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
__block NSString *masterPassword = nil; return PearlAwait( (id)^(void (^setResult)(id)) {
dispatch_group_t userPasswordGroup = dispatch_group_create();
dispatch_group_enter( userPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName ) [PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName )
message:strf( @"Imports %lu sites, overwriting %lu.", message:strf( @"Imports %lu sites, overwriting %lu.",
(unsigned long)importCount, (unsigned long)deleteCount ) (unsigned long)importCount, (unsigned long)deleteCount )
viewStyle:UIAlertViewStyleSecureTextInput viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try { if (buttonIndex_ == [alert_ cancelButtonIndex])
if (buttonIndex_ == [alert_ cancelButtonIndex]) setResult( nil );
return; else
setResult( [alert_ textFieldAtIndex:0].text );
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( userPasswordGroup );
}
} }
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil]; cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
} ); } );
dispatch_group_wait( userPasswordGroup, DISPATCH_TIME_FOREVER );
return masterPassword;
}]; }];
switch (result) { switch (result) {

File diff suppressed because it is too large Load Diff