diff --git a/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword-iOS.xcodeproj/project.pbxproj index 65db9bb8..079dfbd4 100644 --- a/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; }; 93D399B873AF89808151D2F5 /* MPAppsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390DABAE4368E90E37340 /* MPAppsViewController.m */; }; 93D39BCE5F69D8EBE7E9F6EC /* MPAppViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E070BD3F45B3045A1DA /* MPAppViewController.m */; }; + 93D39BD0F5C7C76678712500 /* MPAllSitesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F16ADA770D8C13B4555 /* MPAllSitesViewController.m */; }; 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */; }; 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; }; @@ -972,6 +973,7 @@ /* Begin PBXFileReference section */ 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = ""; }; + 93D390B3209212B3049BEC2D /* MPAllSitesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAllSitesViewController.h; sourceTree = ""; }; 93D390DABAE4368E90E37340 /* MPAppsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppsViewController.m; sourceTree = ""; }; 93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV0.m; sourceTree = ""; }; 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = ""; }; @@ -989,6 +991,8 @@ 93D39E070BD3F45B3045A1DA /* MPAppViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppViewController.m; sourceTree = ""; }; 93D39E81EFABC6085AC8AE69 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = ""; }; 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV1.m; sourceTree = ""; }; + 93D39F16ADA770D8C13B4555 /* MPAllSitesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAllSitesViewController.m; sourceTree = ""; }; + 93D39F37240730C6311B8FBD /* MPElementPickerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementPickerDelegate.h; sourceTree = ""; }; 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlEMail.h; sourceTree = ""; }; DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; DA0A1D0315690A9A0092735D /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = Resources/Default.png; sourceTree = SOURCE_ROOT; }; @@ -2542,6 +2546,9 @@ DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */, DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */, DAB8D45415036BCF00CED3BC /* Settings.bundle */, + 93D39F16ADA770D8C13B4555 /* MPAllSitesViewController.m */, + 93D390B3209212B3049BEC2D /* MPAllSitesViewController.h */, + 93D39F37240730C6311B8FBD /* MPElementPickerDelegate.h */, ); path = iOS; sourceTree = ""; @@ -4716,6 +4723,7 @@ DA81253816B8546B00F4732F /* MPElementStoredEntity.m in Sources */, DA81253B16B8546B00F4732F /* MPUserEntity.m in Sources */, DA81253E16B8546C00F4732F /* MPElementGeneratedEntity.m in Sources */, + 93D39BD0F5C7C76678712500 /* MPAllSitesViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MasterPassword/MPAppDelegate_Shared.h b/MasterPassword/MPAppDelegate_Shared.h index 13b39e51..53efe70b 100644 --- a/MasterPassword/MPAppDelegate_Shared.h +++ b/MasterPassword/MPAppDelegate_Shared.h @@ -18,6 +18,8 @@ @property (strong, nonatomic) MPUserEntity *activeUser; @property (strong, nonatomic) MPKey *key; ++ (instancetype)get; + - (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc; @end diff --git a/MasterPassword/MPConfig.m b/MasterPassword/MPConfig.m index 71d553c0..43009015 100644 --- a/MasterPassword/MPConfig.m +++ b/MasterPassword/MPConfig.m @@ -28,9 +28,4 @@ return self; } -+ (MPConfig *)get { - - return (MPConfig *)[super get]; -} - @end diff --git a/MasterPassword/Mac/MPAppDelegate.m b/MasterPassword/Mac/MPAppDelegate.m index 842cdde8..df9065db 100644 --- a/MasterPassword/Mac/MPAppDelegate.m +++ b/MasterPassword/Mac/MPAppDelegate.m @@ -42,11 +42,6 @@ static EventHotKeyID MPLockHotKey = {.signature = 'lock', .id = 1}; }); } -+ (MPAppDelegate *)get { - - return (MPAppDelegate *)[super get]; -} - static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { // Extract the hotkey ID. diff --git a/MasterPassword/Mac/MPMacConfig.m b/MasterPassword/Mac/MPMacConfig.m index 708ca2ed..ef5a4e03 100644 --- a/MasterPassword/Mac/MPMacConfig.m +++ b/MasterPassword/Mac/MPMacConfig.m @@ -20,9 +20,4 @@ return self; } -+ (MPMacConfig *)get { - - return (MPMacConfig *)[super get]; -} - @end diff --git a/MasterPassword/iOS/MPAllSitesViewController.h b/MasterPassword/iOS/MPAllSitesViewController.h new file mode 100644 index 00000000..c5691760 --- /dev/null +++ b/MasterPassword/iOS/MPAllSitesViewController.h @@ -0,0 +1,28 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAllSitesViewController +// +// Created by Maarten Billemont on 2013-01-31. +// Copyright 2013 lhunath (Maarten Billemont). All rights reserved. +// + +#import + +#import "MPElementPickerDelegate.h" + +@interface MPAllSitesViewController : UITableViewController + +@property (weak, nonatomic) IBOutlet id delegate; + +- (IBAction)close:(id)sender; + +@end diff --git a/MasterPassword/iOS/MPAllSitesViewController.m b/MasterPassword/iOS/MPAllSitesViewController.m new file mode 100644 index 00000000..2714e44b --- /dev/null +++ b/MasterPassword/iOS/MPAllSitesViewController.m @@ -0,0 +1,240 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAllSitesViewController +// +// Created by Maarten Billemont on 2013-01-31. +// Copyright 2013 lhunath (Maarten Billemont). All rights reserved. +// + +#import "MPAllSitesViewController.h" + +#import "MPAppDelegate.h" +#import "MPAppDelegate_Store.h" + + +@interface MPAllSitesViewController() + +@property (nonatomic,strong)NSDateFormatter *dateFormatter; + +@end + +@implementation MPAllSitesViewController { + + NSFetchedResultsController *_fetchedResultsController; +} + + +- (void)viewDidLoad { + + [super viewDidLoad]; + + self.dateFormatter = [NSDateFormatter new]; + self.dateFormatter.dateStyle = NSDateFormatterShortStyle; +} + +- (IBAction)close:(id)sender { + + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (NSFetchedResultsController *)fetchedResultsController { + + if (!_fetchedResultsController) { + NSAssert([[NSThread currentThread] isMainThread], @"The fetchedResultsController must run on the main thread."); + NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextForThreadIfReady]; + if (!moc) + return nil; + + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; + fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]]; + fetchRequest.fetchBatchSize = 20; + _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc + sectionNameKeyPath:nil cacheName:nil]; + _fetchedResultsController.delegate = self; + } + + return _fetchedResultsController; +} + +- (void)fetchData { + + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; + if (!activeUser) + return; + + self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"user == %@", activeUser]; + + NSError *error; + if (![self.fetchedResultsController performFetch:&error]) + err(@"Couldn't fetch elements: %@", error); + + [self.tableView reloadData]; +} + +- (void)viewWillAppear:(BOOL)animated { + + [super viewWillAppear:animated]; + + [self fetchData]; +} + + +// See MP-14, also crashes easily on internal assertions etc.. +//- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { +// +// [self.searchDisplayController.searchResultsTableView beginUpdates]; +//} +// +//- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject +// atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { +// +// UITableView *tableView = self.searchDisplayController.searchResultsTableView; +// switch(type) { +// +// case NSFetchedResultsChangeInsert: +// [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; +// break; +// +// case NSFetchedResultsChangeDelete: +// [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; +// break; +// +// case NSFetchedResultsChangeUpdate: +// [self configureCell:[tableView cellForRowAtIndexPath:indexPath] +// inTableView:tableView atIndexPath:indexPath]; +// break; +// +// case NSFetchedResultsChangeMove: +// [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] +// withRowAnimation:UITableViewRowAnimationFade]; +// [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] +// withRowAnimation:UITableViewRowAnimationFade]; +// break; +// } +//} +// +// +//- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id)sectionInfo +// atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { +// +// UITableView *tableView = self.searchDisplayController.searchResultsTableView; +// switch(type) { +// +// case NSFetchedResultsChangeInsert: +// [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] +// withRowAnimation:UITableViewRowAnimationFade]; +// break; +// +// case NSFetchedResultsChangeDelete: +// [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] +// withRowAnimation:UITableViewRowAnimationFade]; +// break; +// } +//} + +- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { + + dbg(@"controllerDidChangeContent on thread: %@", [NSThread currentThread].name); + [self.tableView reloadData]; + //[self.tableView endUpdates]; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + + return (NSInteger)[[self.fetchedResultsController sections] count]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + + return (NSInteger)[[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] numberOfObjects]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"]; + if (!cell.backgroundView) { +// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"]; + + UIImage *backgroundImage = [[UIImage imageNamed:@"ui_list_middle"] resizableImageWithCapInsets:UIEdgeInsetsMake(3, 3, 3, 3) + resizingMode:UIImageResizingModeStretch]; + UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage]; + backgroundImageView.frame = CGRectMake(-5, 0, 330, 34); + backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)]; + [backgroundView addSubview:backgroundImageView]; + backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + cell.backgroundView = backgroundView; +// cell.textLabel.backgroundColor = [UIColor clearColor]; +// cell.textLabel.textColor = [UIColor whiteColor]; +// cell.detailTextLabel.backgroundColor = [UIColor clearColor]; +// cell.detailTextLabel.textColor = [UIColor lightGrayColor]; +// cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +// cell.clipsToBounds = YES; + } + + [self configureCell:cell inTableView:tableView atIndexPath:indexPath]; + + return cell; +} + +- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath { + + MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; + + cell.textLabel.text = element.name; + cell.detailTextLabel.text = PearlString(@"Used %d times, last on %@", + element.uses, [self.dateFormatter stringFromDate:element.lastUsed]); +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + + [self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]]; + [self close:nil]; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + + return [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name]; +} + +- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { + + return [self.fetchedResultsController sectionIndexTitles]; +} + +- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { + + return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index]; +} + +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle +forRowAtIndexPath:(NSIndexPath *)indexPath { + + if (editingStyle == UITableViewCellEditingStyleDelete) + [self.fetchedResultsController.managedObjectContext performBlock:^{ + MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; + + inf(@"Deleting element: %@", element.name); + [self.fetchedResultsController.managedObjectContext deleteObject:element]; + +#ifdef TESTFLIGHT_SDK_VERSION + [TestFlight passCheckpoint:MPCheckpointDeleteElement]; +#endif + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointDeleteElement attributes:@{ + @"type" : element.typeName, + @"version" : @(element.version)}]; + }]; +} + + +@end diff --git a/MasterPassword/iOS/MPAppsViewController.m b/MasterPassword/iOS/MPAppsViewController.m index 3eed7543..75820638 100644 --- a/MasterPassword/iOS/MPAppsViewController.m +++ b/MasterPassword/iOS/MPAppsViewController.m @@ -92,6 +92,16 @@ [super viewWillDisappear:animated]; } +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + + return UIInterfaceOrientationPortrait; +} + +- (BOOL)shouldAutorotate { + + return NO; +} + - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController { diff --git a/MasterPassword/iOS/MPElementPickerDelegate.h b/MasterPassword/iOS/MPElementPickerDelegate.h new file mode 100644 index 00000000..2c62a42e --- /dev/null +++ b/MasterPassword/iOS/MPElementPickerDelegate.h @@ -0,0 +1,24 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPElementPickerDelegate +// +// Created by Maarten Billemont on 2013-01-31. +// Copyright 2013 lhunath (Maarten Billemont). All rights reserved. +// + +#import "MPElementEntity.h" + +@protocol MPElementPickerDelegate + +- (void)didSelectElement:(MPElementEntity *)element; + +@end diff --git a/MasterPassword/iOS/MPMainViewController.h b/MasterPassword/iOS/MPMainViewController.h index b7bd4b0a..2e3bb851 100644 --- a/MasterPassword/iOS/MPMainViewController.h +++ b/MasterPassword/iOS/MPMainViewController.h @@ -10,7 +10,7 @@ #import "MPTypeViewController.h" #import "MPSearchDelegate.h" -@interface MPMainViewController : UIViewController +@interface MPMainViewController : UIViewController @property (assign, nonatomic) BOOL siteInfoHidden; @property (strong, nonatomic) IBOutlet MPSearchDelegate *searchDelegate; diff --git a/MasterPassword/iOS/MPMainViewController.m b/MasterPassword/iOS/MPMainViewController.m index 133db9a8..8dc017bc 100644 --- a/MasterPassword/iOS/MPMainViewController.m +++ b/MasterPassword/iOS/MPMainViewController.m @@ -10,6 +10,7 @@ #import "MPAppDelegate.h" #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" +#import "MPAllSitesViewController.h" @interface MPMainViewController() @@ -48,6 +49,8 @@ if ([[segue identifier] isEqualToString:@"MP_ChooseType"]) ((MPTypeViewController *)[segue destinationViewController]).delegate = self; + if ([[segue identifier] isEqualToString:@"MP_AllSites"]) + ((MPAllSitesViewController *)[((UINavigationController *)[segue destinationViewController]) topViewController]).delegate = self; } - (void)viewDidLoad { @@ -753,12 +756,12 @@ } case 2: { inf(@"Action: Preferences"); - [self performSegueWithIdentifier:@"UserProfile" sender:self]; + [self performSegueWithIdentifier:@"MP_UserProfile" sender:self]; break; } case 3: { inf(@"Action: Other Apps"); - [self performSegueWithIdentifier:@"OtherApps" sender:self]; + [self performSegueWithIdentifier:@"MP_OtherApps" sender:self]; break; } //#if defined(ADHOC) && defined(TESTFLIGHT_SDK_VERSION) diff --git a/MasterPassword/iOS/MPSearchDelegate.h b/MasterPassword/iOS/MPSearchDelegate.h index 0bb918e0..a290caa9 100644 --- a/MasterPassword/iOS/MPSearchDelegate.h +++ b/MasterPassword/iOS/MPSearchDelegate.h @@ -7,27 +7,22 @@ // #import + #import "MPElementEntity.h" +#import "MPElementPickerDelegate.h" typedef enum { MPSearchScopeAll, MPSearchScopeOutdated, } MPSearchScope; -@protocol MPSearchResultsDelegate - -- (void)didSelectElement:(MPElementEntity *)element; - -@end - @interface MPSearchDelegate : NSObject @property (strong, nonatomic) NSDateFormatter *dateFormatter; @property (strong, readonly) NSFetchedResultsController *fetchedResultsController; -@property (strong, nonatomic) NSString *query; @property (strong, nonatomic) UILabel *tipView; -@property (weak, nonatomic) IBOutlet id delegate; +@property (weak, nonatomic) IBOutlet id delegate; @property (strong, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController; @property (weak, nonatomic) IBOutlet UIView *searchTipContainer; diff --git a/MasterPassword/iOS/MPSearchDelegate.m b/MasterPassword/iOS/MPSearchDelegate.m index 57e2a102..956a9ecd 100644 --- a/MasterPassword/iOS/MPSearchDelegate.m +++ b/MasterPassword/iOS/MPSearchDelegate.m @@ -9,12 +9,7 @@ #import "MPSearchDelegate.h" #import "MPAppDelegate.h" #import "MPAppDelegate_Store.h" - -@interface MPSearchDelegate (Private) - -- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath; - -@end +#import "MPMainViewController.h" @implementation MPSearchDelegate { @@ -28,7 +23,6 @@ self.dateFormatter = [NSDateFormatter new]; self.dateFormatter.dateStyle = NSDateFormatterShortStyle; - self.query = @""; self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)]; self.tipView.textAlignment = NSTextAlignmentCenter; @@ -60,6 +54,7 @@ NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]]; + fetchRequest.fetchBatchSize = 20; _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc sectionNameKeyPath:nil cacheName:nil]; _fetchedResultsController.delegate = self; @@ -68,6 +63,11 @@ return _fetchedResultsController; } +- (void)searchBarBookmarkButtonClicked:(UISearchBar *)searchBar { + + [((MPMainViewController *)self.delegate) performSegueWithIdentifier:@"MP_AllSites" sender:self]; +} + - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { UITableView *tableView = self.searchDisplayController.searchResultsTableView; @@ -89,12 +89,6 @@ - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { - if (searchBar.searchResultsButtonSelected && !searchText.length) - searchBar.text = @" "; - - self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if (!self.query) - self.query = @""; } - (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller { @@ -113,8 +107,6 @@ - (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller { - controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @""; - self.query = @""; } - (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller { @@ -154,31 +146,30 @@ - (void)fetchData { + + NSString *query = [self.searchDisplayController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if (!query) + return; + MPUserEntity *activeUser = [MPAppDelegate get].activeUser; - assert(self.query); - assert(activeUser); - - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@", activeUser]; - if (self.query.length) - predicate = [NSCompoundPredicate - andPredicateWithSubpredicates:@[[NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", self.query], - predicate]]; + if (!activeUser) + return; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@ AND name BEGINSWITH[cd] %@", activeUser, query]; switch ((MPSearchScope)self.searchDisplayController.searchBar.selectedScopeButtonIndex) { case MPSearchScopeAll: break; case MPSearchScopeOutdated: predicate = [NSCompoundPredicate - andPredicateWithSubpredicates:@[[NSPredicate predicateWithFormat:@"requiresExplicitMigration_ == YES"], - predicate]]; + andPredicateWithSubpredicates:@[[NSPredicate predicateWithFormat:@"requiresExplicitMigration_ == YES"], predicate]]; break; } self.fetchedResultsController.fetchRequest.predicate = predicate; NSError *error; if (![self.fetchedResultsController performFetch:&error]) - err(@"Couldn't fetch elements: %@", error); + err(@"Couldn't fetch elements: %@", error); [self.searchDisplayController.searchBar.superview enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { CGRect searchBarFrame = self.searchDisplayController.searchBar.frame; @@ -258,12 +249,13 @@ NSArray *sections = [self.fetchedResultsController sections]; NSUInteger sectionCount = [sections count]; - if ([self.query length]) { + NSString *query = [self.searchDisplayController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if ([query length]) { __block BOOL hasExactQueryMatch = NO; [sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { id sectionInfo = obj; [[sectionInfo objects] enumerateObjectsUsingBlock:^(id obj_, NSUInteger idx_, BOOL *stop_) { - if ([[obj_ name] isEqualToString:self.query]) { + if ([[obj_ name] isEqualToString:query]) { hasExactQueryMatch = YES; *stop_ = YES; } @@ -327,7 +319,8 @@ element.uses, [self.dateFormatter stringFromDate:element.lastUsed]]; } else { // "New" section - cell.textLabel.text = self.query; + NSString *query = [self.searchDisplayController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + cell.textLabel.text = query; cell.detailTextLabel.text = @"Create a new site."; } } @@ -339,7 +332,7 @@ else { // "New" section. - NSString *siteName = self.query; + NSString *siteName = [self.searchDisplayController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; [PearlAlert showAlertWithTitle:@"New Site" message:PearlString(@"Do you want to create a new site named:\n%@", siteName) viewStyle:UIAlertViewStyleDefault diff --git a/MasterPassword/iOS/MPUnlockViewController.m b/MasterPassword/iOS/MPUnlockViewController.m index 33058569..edee334d 100644 --- a/MasterPassword/iOS/MPUnlockViewController.m +++ b/MasterPassword/iOS/MPUnlockViewController.m @@ -496,7 +496,6 @@ // Lay out user name label. self.nameLabel.text = targetedAvatar? (targetedUser? targetedUser.name: @"New User"): nil; - dbg(@"targetedAvatar: %@, targetedUser: %@, nameLabel: %@", targetedAvatar, targetedUser, self.nameLabel.text); self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds, [self.nameLabel.text sizeWithFont:self.nameLabel.font constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100) diff --git a/MasterPassword/iOS/MPiOSConfig.m b/MasterPassword/iOS/MPiOSConfig.m index fadfd4a8..dfb2c350 100644 --- a/MasterPassword/iOS/MPiOSConfig.m +++ b/MasterPassword/iOS/MPiOSConfig.m @@ -25,9 +25,4 @@ return self; } -+ (MPiOSConfig *)get { - - return (MPiOSConfig *)[super get]; -} - @end diff --git a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard index b2b1670c..25cf4613 100644 --- a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard +++ b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard @@ -633,7 +633,7 @@ Your passwords will be AES-encrypted with your master password. - + @@ -643,25 +643,6 @@ Your passwords will be AES-encrypted with your master password. Outdated - - - - - - - - - - - - - @@ -685,6 +666,25 @@ Your passwords will be AES-encrypted with your master password. + + + + + + + + + + + + + @@ -935,8 +935,9 @@ L4m3P4sSw0rD - - + + + @@ -1214,7 +1215,7 @@ Pink fluffy door frame. - + @@ -1628,6 +1629,77 @@ You could use the word wall for inspiration in finding a memorable master passw + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2077,13 +2149,13 @@ You could use the word wall for inspiration in finding a memorable master passw - + - + @@ -2121,6 +2193,146 @@ You could use the word wall for inspiration in finding a memorable master passw + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/MasterPassword.sketch/fonts b/Resources/MasterPassword.sketch/fonts new file mode 100644 index 00000000..0a1ef4cb --- /dev/null +++ b/Resources/MasterPassword.sketch/fonts @@ -0,0 +1,6 @@ +SourceCodePro-Light +LucidaGrande +HelveticaNeue-Medium +LucidaGrande +HelveticaNeue-Light +Inconsolata \ No newline at end of file diff --git a/Site/algorithm.html b/Site/algorithm.html index 9ef3771e..199f5221 100644 --- a/Site/algorithm.html +++ b/Site/algorithm.html @@ -341,20 +341,7 @@

- By default, Master Password uses the Long Password type for any new passwords. The user is able to choose a different password type, which is normally only done if the site's password policy is incompatible with the output password produced by this type. -

-

- To create the create the output password, the bytes in the seed are encoded according to the template. The first seed byte is used to determine which of the type's templates to use for encoding an output password. We take the byte value of the first seed byte modulo the amount of templates set for the chosen password type and use the result as a zero-based index in the template list for the password type. -

-
-                templates = [ "CvcvCvcvnoCvcv", "CvcvnoCvcvCvcv", "CvcvCvcvCvcvno", ... ]
-                template  = templates[ seed[0] % count( templates ) ]
-            
-

- Now that we know what template to use for building our output password, all that's left is to iterate the template, and produce a character of password output for each step. When we iterate the template (index i), we look in the character group identified by the character (string passChars) in the template at index i. -

-

- The following character groups (passChars) are defined: + Where each of the letters above expand any of the characters in their respective character group:

  • Template character: V

      @@ -403,6 +390,19 @@

    +

    + By default, Master Password uses the Long Password type for any new passwords. The user is able to choose a different password type, which is normally only done if the site's password policy is incompatible with the output password produced by this type. +

    +

    + To create the output password, the bytes in the seed are encoded according to the template. The first seed byte is used to determine which of the type's templates to use for encoding an output password. We take the byte value of the first seed byte modulo the amount of templates set for the chosen password type and use the result as a zero-based index in the template list for the password type. +

    +
    +                templates = [ "CvcvCvcvnoCvcv", "CvcvnoCvcvCvcv", "CvcvCvcvCvcvno", ... ]
    +                template  = templates[ seed[0] % count( templates ) ]
    +            
    +

    + Now that we know what template to use for building our output password, all that's left is to iterate the template, and produce a character of password output for each step. When we iterate the template (index i), we look in the character group identified by the character (string passChars) in the template at index i. +

    We use the seed's byte value at index i + 1 modulo the amount of characters in the character class to determine which character (passChar) in the class to use for the output password at index i.