2
0

Bugfixes with regards to swiping password types.

This commit is contained in:
Maarten Billemont 2014-05-13 07:27:11 -04:00
parent 943c378206
commit fa57b8817b
13 changed files with 135 additions and 255 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit 7ed74fd245f47339903ab2faeb3f36754155452c Subproject commit d37640cc0133e56b1d58baf7ed44ce69dcb20f2f

View File

@ -21,7 +21,7 @@
+ (instancetype)get; + (instancetype)get;
- (MPUserEntity *)activeUserForMainThread; - (MPUserEntity *)activeUserForMainThread;
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc; - (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context;
- (void)setActiveUser:(MPUserEntity *)activeUser; - (void)setActiveUser:(MPUserEntity *)activeUser;
@end @end

View File

@ -28,18 +28,15 @@
return [self activeUserInContext:[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady]]; return [self activeUserInContext:[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady]];
} }
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc { - (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context {
NSManagedObjectID *activeUserOID = self.activeUserOID; NSManagedObjectID *activeUserOID = self.activeUserOID;
if (!activeUserOID || !moc) if (!activeUserOID || !context)
return nil; return nil;
NSError *error; MPUserEntity *activeUser = [MPUserEntity existingObjectWithID:activeUserOID inContext:context];
MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:activeUserOID error:&error]; if (!activeUser)
if (!activeUser) {
[self signOutAnimated:YES]; [self signOutAnimated:YES];
err(@"Failed to retrieve active user: %@", error);
}
return activeUser; return activeUser;
} }

View File

@ -260,12 +260,9 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) { if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectID *elementOID = [self elementForTableIndexPath:indexPath].objectID; NSManagedObjectID *elementOID = [self elementForTableIndexPath:indexPath].objectID;
[MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSError *error = nil; MPElementEntity *element = [MPElementEntity existingObjectWithID:elementOID inContext:context];
MPElementEntity *element = (MPElementEntity *)[context existingObjectWithID:elementOID error:&error]; if (!element)
if (!element) {
err(@"Failed to retrieve element to delete: %@", error);
return; return;
}
inf(@"Deleting element: %@", element.name); inf(@"Deleting element: %@", element.name);
[context deleteObject:element]; [context deleteObject:element];

View File

@ -18,10 +18,12 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "MPPasswordCell.h" #import "MPPasswordCell.h"
#import "MPPasswordsViewController.h"
@interface MPPasswordElementCell : MPPasswordCell @interface MPPasswordElementCell : MPPasswordCell
@property(nonatomic, copy) NSString *transientSite; @property(nonatomic, copy) NSString *transientSite;
@property(nonatomic, weak) MPPasswordsViewController *passwordsViewController;
- (MPElementEntity *)mainElement; - (MPElementEntity *)mainElement;
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context; - (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context;

View File

@ -69,15 +69,7 @@
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context { - (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context {
if (!_elementOID) return [MPElementEntity existingObjectWithID:_elementOID inContext:context];
return nil;
NSError *error = nil;
MPElementEntity *element = _elementOID? (MPElementEntity *)[context existingObjectWithID:_elementOID error:&error]: nil;
if (_elementOID && !element)
err(@"Failed to load element: %@", error);
return element;
} }
- (void)reloadData { - (void)reloadData {

View File

@ -92,8 +92,17 @@
[super reloadWithElement:mainElement]; [super reloadWithElement:mainElement];
if (!mainElement) {
self.loginButton.alpha = 0;
self.nameLabel.text = @"";
self.contentField.text = @"";
return;
}
self.nameLabel.alpha = 1;
self.loginButton.alpha = 1; self.loginButton.alpha = 1;
self.typeLabel.text = [mainElement.algorithm nameOfType:self.type]; if (self.type != (MPElementType)NSNotFound)
self.typeLabel.text = [mainElement.algorithm nameOfType:self.type];
if (mainElement.requiresExplicitMigration) if (mainElement.requiresExplicitMigration)
self.upgradeButton.alpha = 1; self.upgradeButton.alpha = 1;
@ -166,6 +175,8 @@
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context]; MPElementEntity *element = [[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context];
if (!element)
return;
switch (self.contentFieldMode) { switch (self.contentFieldMode) {
case MPContentFieldModePassword: case MPContentFieldModePassword:

View File

@ -20,6 +20,7 @@
#import "MPCell.h" #import "MPCell.h"
#import "MPPasswordCell.h" #import "MPPasswordCell.h"
#import "MPPasswordElementCell.h" #import "MPPasswordElementCell.h"
#import "MPPasswordsViewController.h"
@interface MPPasswordTypesCell : MPPasswordElementCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> @interface MPPasswordTypesCell : MPPasswordElementCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

View File

@ -68,9 +68,7 @@
[super applyLayoutAttributes:layoutAttributes]; [super applyLayoutAttributes:layoutAttributes];
[self.contentCollectionView.collectionViewLayout invalidateLayout]; [self.contentCollectionView.collectionViewLayout invalidateLayout];
if (self.activeType) [self scrollToActiveType];
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:self.activeType]
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
} }
- (void)reloadWithTransientSite:(NSString *)siteName { - (void)reloadWithTransientSite:(NSString *)siteName {
@ -104,7 +102,10 @@
if (!self.algorithm) if (!self.algorithm)
dbg_return_tr( 0, @, @(section) ); dbg_return_tr( 0, @, @(section) );
dbg_return_tr( [[self.algorithm allTypes] count] + 1, @, @(section) ); if (self.transientSite)
dbg_return_tr( [[self.algorithm allTypes] count], @, @(section) );
dbg_return_tr( [[self.algorithm allTypes] count] + 1 /* Delete */, @, @(section) );
} }
- (MPPasswordLargeCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - (MPPasswordLargeCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
@ -125,16 +126,13 @@
NSString *newSiteName = self.transientSite; NSString *newSiteName = self.transientSite;
if (newSiteName) { if (newSiteName) {
[[UIResponder findFirstResponder] resignFirstResponder];
[PearlAlert showAlertWithTitle:@"Create Site" [PearlAlert showAlertWithTitle:@"Create Site"
message:strf( @"Do you want to create a new site named:\n%@", newSiteName ) message:strf( @"Do you want to create a new site named:\n%@", newSiteName )
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) { if (buttonIndex == [alert cancelButtonIndex]) {
// Cancel // Cancel
// NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems]) for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES]; [collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
return; return;
@ -142,70 +140,56 @@
// Create // Create
[[MPiOSAppDelegate get] addElementNamed:newSiteName completion:^(MPElementEntity *element) { [[MPiOSAppDelegate get] addElementNamed:newSiteName completion:^(MPElementEntity *element) {
[self copyContentOfElement:element];
PearlMainQueue( ^{ PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Added %@", newSiteName ) dismissAfter:2]; [self.passwordsViewController updatePasswords];
PearlMainQueueAfter( 0.2f, ^{
// NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
} );
} ); } );
}]; }];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
return; return;
} }
MPElementEntity *element = [self mainElement]; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
if (!element) { BOOL used = NO;
// [collectionView selectItemAtIndexPath:indexPath animated:NO MPElementEntity *element = [self elementInContext:context];
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; if (!element)
// [collectionView deselectItemAtIndexPath:indexPath animated:YES]; wrn(@"No element to use for: %@", self);
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems]) else if (indexPath.item == 0) {
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES]; [context deleteObject:element];
return; [context saveToStore];
} } else
used = [self copyContentOfElement:element];
PearlMainQueueAfter( 0.2f, ^{
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
if (used)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context_) {
[[self elementInContext:context_] use];
[context_ saveToStore];
}];
} );
}];
}
- (BOOL)copyContentOfElement:(MPElementEntity *)element {
inf( @"Copying password for: %@", element.name ); inf( @"Copying password for: %@", element.name );
MPCheckpoint( MPCheckpointCopyToPasteboard, @{ MPCheckpoint( MPCheckpointCopyToPasteboard, @{
@"type" : NilToNSNull( element.typeName ), @"type" : NilToNSNull( element.typeName ),
@"version" : @(element.version), @"version" : @(element.version),
@"emergency" : @NO @"emergency" : @NO
} );
[element resolveContentUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
if (![result length]) {
PearlMainQueue( ^{
// NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
} ); } );
return;
}
NSString *result = [element resolveContentUsingKey:[MPAppDelegate_Shared get].key];
if ([result length]) {
[UIPasteboard generalPasteboard].string = result; [UIPasteboard generalPasteboard].string = result;
PearlMainQueue( ^{ [PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2];
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2]; return YES;
PearlMainQueueAfter( 0.2f, ^{ }
// NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { return NO;
[[self elementInContext:context] use];
[context saveToStore];
}];
} );
} );
}];
} }
#pragma mark - UIScrollViewDelegate #pragma mark - UIScrollViewDelegate
@ -235,8 +219,18 @@
#pragma mark - Private #pragma mark - Private
- (void)scrollToActiveType {
if (self.activeType && self.activeType != (MPElementType)NSNotFound)
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:self.activeType]
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
- (MPElementType)typeForContentIndexPath:(NSIndexPath *)indexPath { - (MPElementType)typeForContentIndexPath:(NSIndexPath *)indexPath {
if (self.transientSite)
return [[self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN][indexPath.item] unsignedIntegerValue];
if (indexPath.item == 0) if (indexPath.item == 0)
return (MPElementType)NSNotFound; return (MPElementType)NSNotFound;
@ -247,20 +241,29 @@
NSArray *types = [self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN]; NSArray *types = [self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN];
for (NSInteger t = 0; t < [types count]; ++t) for (NSInteger t = 0; t < [types count]; ++t)
if ([types[t] unsignedIntegerValue] == type) if ([types[t] unsignedIntegerValue] == type) {
return [NSIndexPath indexPathForItem:t + 1 inSection:0]; if (self.transientSite)
return [NSIndexPath indexPathForItem:t inSection:0];
else
return [NSIndexPath indexPathForItem:t + 1 inSection:0];
}
Throw(@"Unsupported type: %d", type); Throw(@"Unsupported type: %lud", (long)type);
} }
- (void)saveContentType { - (void)saveContentType {
if (self.transientSite)
return;
CGPoint centerPoint = CGPointFromCGRectCenter( self.contentCollectionView.bounds ); CGPoint centerPoint = CGPointFromCGRectCenter( self.contentCollectionView.bounds );
NSIndexPath *centerIndexPath = [self.contentCollectionView indexPathForItemAtPoint:centerPoint]; NSIndexPath *centerIndexPath = [self.contentCollectionView indexPathForItemAtPoint:centerPoint];
self.activeType = [self typeForContentIndexPath:centerIndexPath]; MPElementType type = [self typeForContentIndexPath:centerIndexPath];
if (type == ((MPElementType)NSNotFound))
// Active cell is not a type cell.
return;
self.activeType = type;
if (self.transientSite)
return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPPasswordLargeCell *cell = (MPPasswordLargeCell *)[self.contentCollectionView cellForItemAtIndexPath:centerIndexPath]; MPPasswordLargeCell *cell = (MPPasswordLargeCell *)[self.contentCollectionView cellForItemAtIndexPath:centerIndexPath];
@ -270,7 +273,7 @@
} }
MPElementEntity *element = [self elementInContext:context]; MPElementEntity *element = [self elementInContext:context];
if (element.type == cell.type) if (!element || element.type == cell.type)
// Nothing changed. // Nothing changed.
return; return;
@ -284,8 +287,7 @@
_activeType = activeType; _activeType = activeType;
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:activeType] [self scrollToActiveType];
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
} }
- (void)setSelected:(BOOL)selected { - (void)setSelected:(BOOL)selected {

View File

@ -36,6 +36,7 @@
@property(nonatomic, readonly) MPCoachmark *coachmark; @property(nonatomic, readonly) MPCoachmark *coachmark;
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void (^)(BOOL finished))completion; - (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void (^)(BOOL finished))completion;
- (void)updatePasswords;
- (IBAction)dismissPopdown:(id)sender; - (IBAction)dismissPopdown:(id)sender;
- (IBAction)signOut:(id)sender; - (IBAction)signOut:(id)sender;

View File

@ -40,7 +40,6 @@
__weak UITapGestureRecognizer *_passwordsDismissRecognizer; __weak UITapGestureRecognizer *_passwordsDismissRecognizer;
NSFetchedResultsController *_fetchedResultsController; NSFetchedResultsController *_fetchedResultsController;
BOOL _exactMatch; BOOL _exactMatch;
NSMutableDictionary *_fetchedUpdates;
UIColor *_backgroundColor; UIColor *_backgroundColor;
UIColor *_darkenedBackgroundColor; UIColor *_darkenedBackgroundColor;
__weak UIViewController *_popdownVC; __weak UIViewController *_popdownVC;
@ -52,7 +51,6 @@
[super viewDidLoad]; [super viewDidLoad];
_fetchedUpdates = [NSMutableDictionary dictionaryWithCapacity:4];
_backgroundColor = self.passwordCollectionView.backgroundColor; _backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f]; _darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_coachmark = [MPCoachmark coachmarkForClass:[self class] version:0]; _coachmark = [MPCoachmark coachmarkForClass:[self class] version:0];
@ -118,11 +116,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
if (collectionView == self.passwordCollectionView) { if (collectionView == self.passwordCollectionView) {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout; UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
CGFloat itemWidth = UIEdgeInsetsInsetRect(self.passwordCollectionView.bounds, layout.sectionInset).size.width; CGFloat itemWidth = UIEdgeInsetsInsetRect(self.passwordCollectionView.bounds, layout.sectionInset).size.width;
return CGSizeMake( itemWidth, 100 );
if (indexPath.item < 3 || indexPath.item >= ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
return CGSizeMake( itemWidth, 100 );
return CGSizeMake( (itemWidth - layout.minimumInteritemSpacing) / 2, 44 );
} }
Throw(@"Unexpected collection view: %@", collectionView); Throw(@"Unexpected collection view: %@", collectionView);
@ -155,14 +149,12 @@ referenceSizeForHeaderInSection:(NSInteger)section {
MPPasswordElementCell *cell; MPPasswordElementCell *cell;
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) { if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) {
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (indexPath.item < 3) cell = [MPPasswordTypesCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
cell = [MPPasswordTypesCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
else
cell = [MPPasswordSmallCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
} }
else else
// New Site. // New Site.
cell = [MPPasswordTypesCell dequeueCellForTransientSite:self.query fromCollectionView:collectionView atIndexPath:indexPath]; cell = [MPPasswordTypesCell dequeueCellForTransientSite:self.query fromCollectionView:collectionView atIndexPath:indexPath];
cell.passwordsViewController = self;
[UIView setAnimationsEnabled:YES]; [UIView setAnimationsEnabled:YES];
dbg_return(cell, indexPath); dbg_return(cell, indexPath);
@ -171,140 +163,30 @@ referenceSizeForHeaderInSection:(NSInteger)section {
Throw(@"Unexpected collection view: %@", collectionView); Throw(@"Unexpected collection view: %@", collectionView);
} }
#pragma mark - NSFetchedResultsControllerDelegate - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath {
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { return [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"MPPasswordHeader" forIndexPath:indexPath];
if (controller == _fetchedResultsController) {
dbg(@"controllerWillChangeContent");
NSAssert(![_fetchedUpdates count], @"Didn't finish a previous change update?");
if ([_fetchedUpdates count]) {
[_fetchedUpdates removeAllObjects];
[self.passwordCollectionView reloadData];
}
}
} }
#pragma mark - NSFetchedResultsControllerDelegate
- (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 {
if (controller == _fetchedResultsController) { if (controller == _fetchedResultsController) {
NSMutableArray *updatesForType = _fetchedUpdates[@(type)]; [self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section]];
if (!updatesForType) [self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section]];
_fetchedUpdates[@(type)] = updatesForType = [NSMutableArray new];
[updatesForType addObject:@{
@"object" : NilToNSNull(anObject),
@"indexPath" : NilToNSNull(indexPath),
@"newIndexPath" : NilToNSNull(newIndexPath)
}];
switch (type) {
case NSFetchedResultsChangeInsert:
dbg(@"didChangeObject: insert: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeDelete:
dbg(@"didChangeObject: delete: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeMove:
dbg(@"didChangeObject: move: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeUpdate:
dbg(@"didChangeObject: update: %@", [updatesForType lastObject]);
break;
}
} }
} }
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
if (controller == _fetchedResultsController) { if (controller == _fetchedResultsController)
NSMutableArray *updatesForType = _fetchedUpdates[@(type << 3)]; [self.passwordCollectionView reloadData];
if (!updatesForType)
_fetchedUpdates[@(type << 3)] = updatesForType = [NSMutableArray new];
[updatesForType addObject:@{
@"sectionInfo" : NilToNSNull(sectionInfo),
@"index" : @(sectionIndex)
}];
switch (type) {
case NSFetchedResultsChangeInsert:
dbg(@"didChangeSection: insert: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeDelete:
dbg(@"didChangeSection: delete: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeMove:
dbg(@"didChangeSection: move: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeUpdate:
dbg(@"didChangeSection: update: %@", [updatesForType lastObject]);
break;
}
}
} }
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (controller == _fetchedResultsController && [_fetchedUpdates count]) {
[self.passwordCollectionView performBatchUpdates:^{
[_fetchedUpdates enumerateKeysAndObjectsUsingBlock:^(NSNumber *typeNumber, NSArray *updates, BOOL *stop) {
BOOL updateIsSection = NO;
NSFetchedResultsChangeType type = [typeNumber unsignedIntegerValue];
if (type >= 1 << 3) {
updateIsSection = YES;
type = type >> 3;
}
switch (type) {
case NSFetchedResultsChangeInsert:
if (updateIsSection) {
for (NSDictionary *update in updates) {
dbg(@"insertSections:%@", update[@"index"]);
[self.passwordCollectionView insertSections:
[NSIndexSet indexSetWithIndex:[update[@"index"] unsignedIntegerValue]]];
}
}
else {
dbg(@"insertItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.newIndexPath"]);
[self.passwordCollectionView insertItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.newIndexPath"]];
}
break;
case NSFetchedResultsChangeDelete:
if (updateIsSection) {
for (NSDictionary *update in updates) {
dbg(@"deleteSections:%@", update[@"index"]);
[self.passwordCollectionView deleteSections:
[NSIndexSet indexSetWithIndex:[update[@"index"] unsignedIntegerValue]]];
}
}
else {
dbg(@"deleteItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.indexPath"]);
[self.passwordCollectionView deleteItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.indexPath"]];
}
break;
case NSFetchedResultsChangeMove:
NSAssert(!updateIsSection, @"Move not supported for sections");
for (NSDictionary *update in updates) {
dbg(@"moveItemAtIndexPath:%@ toIndexPath:%@", update[@"indexPath"], update[@"newIndexPath"]);
[self.passwordCollectionView moveItemAtIndexPath:update[@"indexPath"] toIndexPath:update[@"newIndexPath"]];
}
break;
case NSFetchedResultsChangeUpdate:
NSAssert(!updateIsSection, @"Update not supported for sections");
dbg(@"reloadItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.indexPath"]);
[self.passwordCollectionView reloadItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.indexPath"]];
break;
}
}];
} completion:nil];
[_fetchedUpdates removeAllObjects];
}
}
#pragma mark - UIScrollViewDelegate
#pragma mark - UISearchBarDelegate #pragma mark - UISearchBarDelegate
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar { - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
@ -319,13 +201,10 @@ referenceSizeForHeaderInSection:(NSInteger)section {
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { - (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
if (searchBar == self.passwordsSearchBar) { if (searchBar == self.passwordsSearchBar)
// _passwordsDismissRecognizer = [self.view dismissKeyboardForField:self.passwordsSearchBar onTouchForced:NO];
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
self.passwordCollectionView.backgroundColor = _darkenedBackgroundColor; self.passwordCollectionView.backgroundColor = _darkenedBackgroundColor;
}]; }];
}
} }
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { - (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {

View File

@ -360,28 +360,31 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if (buttonIndex == [sheet destructiveButtonIndex]) { if (buttonIndex == [sheet destructiveButtonIndex]) {
// Delete User // Delete User
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSManagedObject *user_ = [context existingObjectWithID:userID error:NULL]; MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (user_) { if (!user_)
[context deleteObject:user_]; return;
[context saveToStore];
} [context deleteObject:user_];
[context saveToStore];
}]; }];
return; return;
} }
if (buttonIndex == [sheet firstOtherButtonIndex]) if (buttonIndex == [sheet firstOtherButtonIndex])
// Reset Password // Reset Password
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user_ = (MPUserEntity *)[context existingObjectWithID:userID error:NULL]; MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (user_) if (!user_)
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{ return;
PearlMainQueue( ^{
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell]; [[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO PearlMainQueue( ^{
scrollPosition:UICollectionViewScrollPositionNone]; NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
[self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath]; [self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
} ); scrollPosition:UICollectionViewScrollPositionNone];
}]; [self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath];
} );
}];
}]; }];
} cancelTitle:[PearlStrings get].commonButtonCancel } cancelTitle:[PearlStrings get].commonButtonCancel
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil]; destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
@ -477,12 +480,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if ((*isNew = indexPath.item >= [self.userIDs count])) if ((*isNew = indexPath.item >= [self.userIDs count]))
return nil; return nil;
NSError *error = nil; return [MPUserEntity existingObjectWithID:self.userIDs[indexPath.item] inContext:context];
MPUserEntity *user = (MPUserEntity *)[context existingObjectWithID:self.userIDs[indexPath.item] error:&error];
if (error)
wrn(@"Failed to load user into context: %@", error);
return user;
} }
- (void)updateAvatars { - (void)updateAvatars {

View File

@ -19,7 +19,7 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="504"/> <rect key="frame" x="0.0" y="0.0" width="320" height="504"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rWM-08-aab" userLabel="Users Root"> <view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rWM-08-aab" userLabel="Users Root">
<rect key="frame" x="0.0" y="0.0" width="320" height="504"/> <rect key="frame" x="0.0" y="0.0" width="320" height="504"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
@ -27,7 +27,7 @@
<rect key="frame" x="142" y="234" width="37" height="37"/> <rect key="frame" x="142" y="234" width="37" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</activityIndicatorView> </activityIndicatorView>
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" alpha="0.0" contentMode="scaleToFill" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="L6J-pd-gcp" userLabel="Avatar Collection"> <collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" alpha="0.0" contentMode="scaleToFill" misplaced="YES" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="L6J-pd-gcp" userLabel="Avatar Collection">
<rect key="frame" x="0.0" y="20" width="320" height="484"/> <rect key="frame" x="0.0" y="20" width="320" height="484"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
@ -122,7 +122,7 @@
<outlet property="delegate" destination="S8q-YF-Kt9" id="det-Eh-phM"/> <outlet property="delegate" destination="S8q-YF-Kt9" id="det-Eh-phM"/>
</connections> </connections>
</collectionView> </collectionView>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar"> <button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
<rect key="frame" x="0.0" y="127" width="44" height="53"/> <rect key="frame" x="0.0" y="127" width="44" height="53"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<constraints> <constraints>
@ -136,7 +136,7 @@
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="lNu-mK-3zD"/> <action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="lNu-mK-3zD"/>
</connections> </connections>
</button> </button>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar"> <button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
<rect key="frame" x="276" y="127" width="44" height="53"/> <rect key="frame" x="276" y="127" width="44" height="53"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<constraints> <constraints>
@ -150,7 +150,7 @@
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="kL5-zV-zbb"/> <action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="kL5-zV-zbb"/>
</connections> </connections>
</button> </button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry" customClass="PearlUIView"> <view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry" customClass="PearlUIView">
<rect key="frame" x="20" y="216" width="280" height="63"/> <rect key="frame" x="20" y="216" width="280" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
@ -1307,7 +1307,7 @@
</connections> </connections>
</collectionViewCell> </collectionViewCell>
</cells> </cells>
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="yzh-hh-YjZ"> <collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPPasswordHeader" id="yzh-hh-YjZ">
<rect key="frame" x="0.0" y="0.0" width="320" height="108"/> <rect key="frame" x="0.0" y="0.0" width="320" height="108"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</collectionReusableView> </collectionReusableView>