2
0

Support for fuzzy searching on iOS.

This commit is contained in:
Maarten Billemont 2014-11-03 12:11:46 -05:00
parent 1c72643aaa
commit 4b2251d4fa
4 changed files with 55 additions and 8 deletions

View File

@ -519,7 +519,7 @@
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ]; fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@", fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
queryPattern, queryPattern, [[MPMacAppDelegate get] activeUserInContext:context]]; queryPattern, queryPattern, [MPMacAppDelegate get].activeUserOID];
NSError *error = nil; NSError *error = nil;
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error]; NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];

View File

@ -27,6 +27,8 @@ typedef NS_ENUM ( NSUInteger, MPPasswordCellMode ) {
@interface MPPasswordCell : MPCell <UIScrollViewDelegate, UITextFieldDelegate> @interface MPPasswordCell : MPCell <UIScrollViewDelegate, UITextFieldDelegate>
@property (nonatomic) NSArray *fuzzyGroups;
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated; - (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated;
- (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated; - (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated;
- (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated; - (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated;

View File

@ -133,6 +133,7 @@
[super prepareForReuse]; [super prepareForReuse];
_siteOID = nil; _siteOID = nil;
_fuzzyGroups = nil;
self.transientSite = nil; self.transientSite = nil;
self.mode = MPPasswordCellModePassword; self.mode = MPPasswordCellModePassword;
[self updateAnimated:NO]; [self updateAnimated:NO];
@ -147,6 +148,15 @@
#pragma mark - State #pragma mark - State
- (void)setFuzzyGroups:(NSArray *)fuzzyGroups {
if (_fuzzyGroups == fuzzyGroups)
return;
_fuzzyGroups = fuzzyGroups;
[self updateSiteName:[self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]];
}
- (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated { - (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated {
if (mode == _mode) if (mode == _mode)
@ -490,8 +500,7 @@
[self.loginNameButton setTitle:@"Tap the pencil to save a username" forState:UIControlStateNormal]; [self.loginNameButton setTitle:@"Tap the pencil to save a username" forState:UIControlStateNormal];
// Site Name // Site Name
self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainSite.name, [self updateSiteName:mainSite];
self.transientSite? @"Tap to create": [mainSite.algorithm shortNameOfType:mainSite.type] );
// Site Password // Site Password
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue]; self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
@ -557,6 +566,26 @@
}]; }];
} }
- (void)updateSiteName:(MPSiteEntity *)site {
NSString *siteName = self.transientSite?: site.name;
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName?: @""];
if ([attributedSiteName length])
for (NSUInteger f = 0, s = (NSUInteger)-1; f < [self.fuzzyGroups count]; ++f) {
s = [siteName rangeOfString:self.fuzzyGroups[f] options:NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch
range:NSMakeRange( s + 1, [siteName length] - (s + 1) )].location;
if (s == NSNotFound)
break;
[attributedSiteName addAttribute:NSBackgroundColorAttributeName value:[UIColor redColor]
range:NSMakeRange( s, [self.fuzzyGroups[f] length] )];
}
[attributedSiteName appendAttributedString:stra(
strf( @" - %@", self.transientSite? @"Tap to create": [site.algorithm shortNameOfType:site.type] ), @{ } )];
self.siteNameLabel.attributedText = attributedSiteName;
}
- (BOOL)copyContentOfSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context { - (BOOL)copyContentOfSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context {
inf( @"Copying password for: %@", site.name ); inf( @"Copying password for: %@", site.name );

View File

@ -44,6 +44,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
BOOL _showTransientItem; BOOL _showTransientItem;
NSUInteger _transientItem; NSUInteger _transientItem;
NSCharacterSet *_siteNameAcceptableCharactersSet; NSCharacterSet *_siteNameAcceptableCharactersSet;
NSArray *_fuzzyGroups;
} }
#pragma mark - Life #pragma mark - Life
@ -147,6 +148,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath]; MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
[cell setFuzzyGroups:_fuzzyGroups];
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
[cell setSite:[self.fetchedResultsController objectAtIndexPath:indexPath] animated:NO]; [cell setSite:[self.fetchedResultsController objectAtIndexPath:indexPath] animated:NO];
else else
@ -253,7 +255,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchBar == self.passwordsSearchBar) { if (searchBar == self.passwordsSearchBar) {
if ([self.query length] && [[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length]) if ([[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length])
[self showTips:MPPasswordsBadNameTip]; [self showTips:MPPasswordsBadNameTip];
[self updatePasswords]; [self updatePasswords];
@ -364,7 +366,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)updatePasswords { - (void)updatePasswords {
NSString *query = self.query;
NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID; NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID;
if (!activeUserOID) { if (!activeUserOID) {
PearlMainQueue( ^{ PearlMainQueue( ^{
@ -375,6 +376,20 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
return; return;
} }
static NSRegularExpression *fuzzyRE;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
} );
NSString *queryString = self.query;
NSString *queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
NSMutableArray *fuzzyGroups = [NSMutableArray arrayWithCapacity:[queryString length]];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
}];
_fuzzyGroups = fuzzyGroups;
[self.fetchedResultsController.managedObjectContext performBlock:^{ [self.fetchedResultsController.managedObjectContext performBlock:^{
NSArray *oldSectionInfos = [self.fetchedResultsController sections]; NSArray *oldSectionInfos = [self.fetchedResultsController sections];
NSMutableArray *oldSections = [[NSMutableArray alloc] initWithCapacity:[oldSectionInfos count]]; NSMutableArray *oldSections = [[NSMutableArray alloc] initWithCapacity:[oldSectionInfos count]];
@ -383,9 +398,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
NSError *error = nil; NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate = self.fetchedResultsController.fetchRequest.predicate =
[query length]? [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
[NSPredicate predicateWithFormat:@"user == %@ AND name BEGINSWITH[cd] %@", activeUserOID, query]: queryPattern, queryPattern, activeUserOID];
[NSPredicate predicateWithFormat:@"user == %@", activeUserOID];
if (![self.fetchedResultsController performFetch:&error]) if (![self.fetchedResultsController performFetch:&error])
err( @"Couldn't fetch sites: %@", [error fullDescription] ); err( @"Couldn't fetch sites: %@", [error fullDescription] );
@ -411,6 +425,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
if (finished) if (finished)
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) [self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top )
animated:YES]; animated:YES];
for (MPPasswordCell *cell in self.passwordCollectionView.visibleCells)
[cell setFuzzyGroups:_fuzzyGroups];
}]; }];
} }
@catch (NSException *exception) { @catch (NSException *exception) {