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:
parent
fcaa5d1d8c
commit
fbbd08790d
@ -161,10 +161,9 @@
|
|||||||
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||||
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||||
|
|
||||||
if ([cell isKindOfClass:[MPGlobalAnswersCell class]]) {
|
if ([cell isKindOfClass:[MPGlobalAnswersCell class]])
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
|
[self copyAnswer:((MPGlobalAnswersCell *)cell).answerField.text];
|
||||||
[UIPasteboard generalPasteboard].string = ((MPGlobalAnswersCell *)cell).answerField.text;
|
|
||||||
}
|
|
||||||
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
|
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
|
||||||
if (!_multiple)
|
if (!_multiple)
|
||||||
[self setMultiple:YES animated:YES];
|
[self setMultiple:YES animated:YES];
|
||||||
@ -192,6 +191,7 @@
|
|||||||
} cancelTitle:@"Cancel" otherTitles:@"Remove Questions", nil];
|
} cancelTitle:@"Cancel" otherTitles:@"Remove Questions", nil];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
|
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
|
||||||
NSString *body;
|
NSString *body;
|
||||||
if (!_multiple) {
|
if (!_multiple) {
|
||||||
@ -215,14 +215,30 @@
|
|||||||
|
|
||||||
[PearlEMail sendEMailTo:nil fromVC:self subject:strf( @"Master Password security answers for %@", site.name ) body:body];
|
[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];
|
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]])
|
||||||
[UIPasteboard generalPasteboard].string = ((MPAnswersQuestionCell *)cell).answerField.text;
|
[self copyAnswer:((MPAnswersQuestionCell *)cell).answerField.text];
|
||||||
}
|
|
||||||
|
|
||||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
[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
|
#pragma mark - Private
|
||||||
|
|
||||||
- (void)updateAnimated:(BOOL)animated {
|
- (void)updateAnimated:(BOOL)animated {
|
||||||
|
@ -84,8 +84,19 @@
|
|||||||
- (IBAction)copyPassword:(id)sender {
|
- (IBAction)copyPassword:(id)sender {
|
||||||
|
|
||||||
NSString *sitePassword = [self.passwordButton titleForState:UIControlStateNormal];
|
NSString *sitePassword = [self.passwordButton titleForState:UIControlStateNormal];
|
||||||
if ([sitePassword length]) {
|
if (![sitePassword length])
|
||||||
[UIPasteboard generalPasteboard].string = sitePassword;
|
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:^{
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
self.tipContainer.visible = YES;
|
self.tipContainer.visible = YES;
|
||||||
} completion:^(BOOL finished) {
|
} completion:^(BOOL finished) {
|
||||||
@ -96,7 +107,6 @@
|
|||||||
} );
|
} );
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
#pragma mark - Private
|
||||||
|
|
||||||
|
@ -493,10 +493,12 @@
|
|||||||
|
|
||||||
- (void)updateAnimated:(BOOL)animated {
|
- (void)updateAnimated:(BOOL)animated {
|
||||||
|
|
||||||
|
Weakify( self );
|
||||||
if (![NSThread isMainThread]) {
|
if (![NSThread isMainThread]) {
|
||||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
PearlMainQueueOperation( ^{
|
||||||
|
Strongify( self );
|
||||||
[self updateAnimated:animated];
|
[self updateAnimated:animated];
|
||||||
}];
|
} );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,11 +546,17 @@
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
// Calculate Fields
|
// Calculate Fields
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
if (![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) {
|
||||||
|
wrn( @"Could not load cell content: key unavailable." );
|
||||||
|
PearlMainQueueOperation( ^{
|
||||||
|
Strongify( self );
|
||||||
|
[self updateAnimated:YES];
|
||||||
|
} );
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BOOL loginGenerated = site.loginGenerated;
|
BOOL loginGenerated = site.loginGenerated;
|
||||||
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
|
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
|
||||||
@ -600,7 +608,13 @@
|
|||||||
else
|
else
|
||||||
self.indicatorView.hidden = YES;
|
self.indicatorView.hidden = YES;
|
||||||
} );
|
} );
|
||||||
}];
|
}]) {
|
||||||
|
wrn( @"Could not load cell content: store unavailable." );
|
||||||
|
PearlMainQueueOperation( ^{
|
||||||
|
Strongify( self );
|
||||||
|
[self updateAnimated:YES];
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
[self.contentView layoutIfNeeded];
|
[self.contentView layoutIfNeeded];
|
||||||
}];
|
}];
|
||||||
@ -635,6 +649,8 @@
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
|
[self.window endEditing:YES];
|
||||||
|
|
||||||
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||||
if ([pasteboard respondsToSelector:@selector(setItems:options:)]) {
|
if ([pasteboard respondsToSelector:@selector(setItems:options:)]) {
|
||||||
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: password } ]
|
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: password } ]
|
||||||
@ -663,8 +679,10 @@
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
|
[self.window endEditing:YES];
|
||||||
|
|
||||||
[UIPasteboard generalPasteboard].string = loginName;
|
[UIPasteboard generalPasteboard].string = loginName;
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
|
||||||
} );
|
} );
|
||||||
|
|
||||||
[site use];
|
[site use];
|
||||||
|
@ -81,7 +81,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
|
|
||||||
[self registerObservers];
|
[self registerObservers];
|
||||||
[self updateConfigKey:nil];
|
[self updateConfigKey:nil];
|
||||||
[self reloadPasswords];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated {
|
- (void)viewDidAppear:(BOOL)animated {
|
||||||
@ -301,17 +300,31 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
|
|
||||||
- (void)registerObservers {
|
- (void)registerObservers {
|
||||||
|
|
||||||
|
static NSRegularExpression *bareHostRE = nil;
|
||||||
|
static dispatch_once_t once = 0;
|
||||||
|
dispatch_once( &once, ^{
|
||||||
|
bareHostRE = [NSRegularExpression regularExpressionWithPattern:@"([^\\.]+\\.[^\\.]+)$" options:0 error:nil];
|
||||||
|
} );
|
||||||
|
|
||||||
PearlRemoveNotificationObservers();
|
PearlRemoveNotificationObservers();
|
||||||
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
|
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
self.passwordSelectionContainer.visible = NO;
|
self.passwordSelectionContainer.visible = NO;
|
||||||
} );
|
} );
|
||||||
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
|
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
|
||||||
[self reloadPasswords];
|
|
||||||
} );
|
|
||||||
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
|
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(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:^{
|
[UIView animateWithDuration:0.7f animations:^{
|
||||||
self.passwordSelectionContainer.visible = YES;
|
self.passwordSelectionContainer.visible = YES;
|
||||||
}];
|
}];
|
||||||
@ -319,9 +332,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
PearlAddNotificationObserver( MPSignedOutNotification, nil, nil,
|
PearlAddNotificationObserver( MPSignedOutNotification, nil, nil,
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
_fetchedResultsController = nil;
|
self->_fetchedResultsController = nil;
|
||||||
self.passwordsSearchBar.text = nil;
|
self.query = nil;
|
||||||
[self.passwordCollectionView reloadData];
|
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
PearlAddNotificationObserver( MPCheckConfigNotification, nil, nil,
|
PearlAddNotificationObserver( MPCheckConfigNotification, nil, nil,
|
||||||
@ -333,9 +345,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
self->_fetchedResultsController = nil;
|
self->_fetchedResultsController = nil;
|
||||||
PearlMainQueue( ^{
|
[self reloadPasswords];
|
||||||
[self.passwordCollectionView reloadData];
|
|
||||||
} );
|
|
||||||
} );
|
} );
|
||||||
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
@ -345,14 +355,13 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
[MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
|
||||||
if (mainContext)
|
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) {
|
||||||
PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil,
|
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
|
||||||
// TODO: either move this into the app delegate or remove the duplicate signOutAnimated: call from the app delegate.
|
// 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];
|
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||||
} );
|
}];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateConfigKey:(NSString *)key {
|
- (void)updateConfigKey:(NSString *)key {
|
||||||
@ -372,11 +381,9 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
|
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
|
||||||
} );
|
} );
|
||||||
|
|
||||||
prof_new( @"updateSites" );
|
|
||||||
NSString *queryString = self.query;
|
NSString *queryString = self.query;
|
||||||
NSString *queryPattern = [[queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1"]
|
NSString *queryPattern = [[queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1"]
|
||||||
stringByAppendingString:@"*"];
|
stringByAppendingString:@"*"];
|
||||||
prof_rewind( @"queryPattern" );
|
|
||||||
NSMutableArray *fuzzyGroups = [NSMutableArray new];
|
NSMutableArray *fuzzyGroups = [NSMutableArray new];
|
||||||
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
|
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
|
||||||
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||||
@ -408,6 +415,12 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setQuery:(NSString *)query {
|
||||||
|
|
||||||
|
self.passwordsSearchBar.text = [query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
|
[self reloadPasswords];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSFetchedResultsController *)fetchedResultsController {
|
- (NSFetchedResultsController *)fetchedResultsController {
|
||||||
|
|
||||||
if (!_fetchedResultsController) {
|
if (!_fetchedResultsController) {
|
||||||
|
Loading…
Reference in New Issue
Block a user