2
0

Pasteboard improvements, UI fixes and site name from pasteboard URL.

[UPDATED]   Timeout after 3 min for other pasteboard copies too.
[FIXED]     Sometimes cell content loading can fail, schedule a retry.
[UPDATED]   Dismiss keyboard when copying content.
[IMPROVED]  Handling of deactivation and reactivation observation.
[ADDED]     When a URL is in the pasteboard, search for the hostname.
This commit is contained in:
Maarten Billemont 2017-04-29 17:50:48 -04:00
parent fcaa5d1d8c
commit fbbd08790d
4 changed files with 105 additions and 48 deletions

View File

@ -161,10 +161,9 @@
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[MPGlobalAnswersCell class]]) {
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
[UIPasteboard generalPasteboard].string = ((MPGlobalAnswersCell *)cell).answerField.text;
}
if ([cell isKindOfClass:[MPGlobalAnswersCell class]])
[self copyAnswer:((MPGlobalAnswersCell *)cell).answerField.text];
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
if (!_multiple)
[self setMultiple:YES animated:YES];
@ -192,6 +191,7 @@
} cancelTitle:@"Cancel" otherTitles:@"Remove Questions", nil];
}
}
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
NSString *body;
if (!_multiple) {
@ -215,14 +215,30 @@
[PearlEMail sendEMailTo:nil fromVC:self subject:strf( @"Master Password security answers for %@", site.name ) body:body];
}
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]]) {
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
[UIPasteboard generalPasteboard].string = ((MPAnswersQuestionCell *)cell).answerField.text;
}
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]])
[self copyAnswer:((MPAnswersQuestionCell *)cell).answerField.text];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)copyAnswer:(NSString *)answer {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector( setItems:options: )]) {
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: answer } ]
options:@{
UIPasteboardOptionLocalOnly : @NO,
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
}];
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied (3 min)" ) dismissAfter:2];
}
else {
pasteboard.string = answer;
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
}
}
#pragma mark - Private
- (void)updateAnimated:(BOOL)animated {

View File

@ -84,8 +84,19 @@
- (IBAction)copyPassword:(id)sender {
NSString *sitePassword = [self.passwordButton titleForState:UIControlStateNormal];
if ([sitePassword length]) {
[UIPasteboard generalPasteboard].string = sitePassword;
if (![sitePassword length])
return;
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector( setItems:options: )])
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: sitePassword } ]
options:@{
UIPasteboardOptionLocalOnly : @NO,
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
}];
else
pasteboard.string = sitePassword;
[UIView animateWithDuration:0.3f animations:^{
self.tipContainer.visible = YES;
} completion:^(BOOL finished) {
@ -95,7 +106,6 @@
}];
} );
}];
}
}
#pragma mark - Private

View File

@ -493,10 +493,12 @@
- (void)updateAnimated:(BOOL)animated {
Weakify( self );
if (![NSThread isMainThread]) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:animated];
}];
} );
return;
}
@ -544,11 +546,17 @@
} );
// Calculate Fields
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *site = [self siteInContext:context];
MPKey *key = [MPiOSAppDelegate get].key;
if (!key)
if (!key) {
wrn( @"Could not load cell content: key unavailable." );
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:YES];
} );
return;
}
BOOL loginGenerated = site.loginGenerated;
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
@ -600,7 +608,13 @@
else
self.indicatorView.hidden = YES;
} );
}];
}]) {
wrn( @"Could not load cell content: store unavailable." );
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:YES];
} );
}
[self.contentView layoutIfNeeded];
}];
@ -635,6 +649,8 @@
return NO;
PearlMainQueue( ^{
[self.window endEditing:YES];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector(setItems:options:)]) {
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: password } ]
@ -663,8 +679,10 @@
return NO;
PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
[self.window endEditing:YES];
[UIPasteboard generalPasteboard].string = loginName;
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
} );
[site use];

View File

@ -81,7 +81,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[self registerObservers];
[self updateConfigKey:nil];
[self reloadPasswords];
}
- (void)viewDidAppear:(BOOL)animated {
@ -301,17 +300,31 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)registerObservers {
static NSRegularExpression *bareHostRE = nil;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
bareHostRE = [NSRegularExpression regularExpressionWithPattern:@"([^\\.]+\\.[^\\.]+)$" options:0 error:nil];
} );
PearlRemoveNotificationObservers();
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
self.passwordSelectionContainer.visible = NO;
} );
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
[self reloadPasswords];
} );
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
NSURL *pasteboardURL = nil;
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector( hasURLs )])
pasteboardURL = pasteboard.hasURLs? pasteboard.URL: nil;
else
pasteboardURL = [NSURL URLWithString:pasteboard.string];
if (pasteboardURL.host)
self.query = NSNullToNil( [pasteboardURL.host firstMatchGroupsOfExpression:bareHostRE][0] );
else
[self reloadPasswords];
[UIView animateWithDuration:0.7f animations:^{
self.passwordSelectionContainer.visible = YES;
}];
@ -319,9 +332,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
PearlAddNotificationObserver( MPSignedOutNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
PearlMainQueue( ^{
_fetchedResultsController = nil;
self.passwordsSearchBar.text = nil;
[self.passwordCollectionView reloadData];
self->_fetchedResultsController = nil;
self.query = nil;
} );
} );
PearlAddNotificationObserver( MPCheckConfigNotification, nil, nil,
@ -333,9 +345,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
self->_fetchedResultsController = nil;
PearlMainQueue( ^{
[self.passwordCollectionView reloadData];
} );
[self reloadPasswords];
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
@ -345,14 +355,13 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
} );
} );
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
if (mainContext)
PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
[MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) {
// 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:mainContext])
[[MPiOSAppDelegate get] signOutAnimated:YES];
} );
}];
}];
}
- (void)updateConfigKey:(NSString *)key {
@ -372,11 +381,9 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
} );
prof_new( @"updateSites" );
NSString *queryString = self.query;
NSString *queryPattern = [[queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1"]
stringByAppendingString:@"*"];
prof_rewind( @"queryPattern" );
NSMutableArray *fuzzyGroups = [NSMutableArray new];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
@ -408,6 +415,12 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
- (void)setQuery:(NSString *)query {
self.passwordsSearchBar.text = [query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
[self reloadPasswords];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (!_fetchedResultsController) {