Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d704f451a3 | ||
|
|
2c9ab5d153 | ||
|
|
d5d33da12f | ||
|
|
cbef1a611b | ||
|
|
0a1f215a1a | ||
|
|
907d2a8ca6 |
2
platform-darwin/External/Pearl
vendored
2
platform-darwin/External/Pearl
vendored
Submodule platform-darwin/External/Pearl updated: 3ceb601ba4...8853d299b2
@@ -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 );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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];
|
||||||
} );
|
} );
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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];
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|||||||
@@ -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
Binary file not shown.
Reference in New Issue
Block a user