Bugfixes with regards to swiping password types.
This commit is contained in:
parent
943c378206
commit
fa57b8817b
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
||||
Subproject commit 7ed74fd245f47339903ab2faeb3f36754155452c
|
||||
Subproject commit d37640cc0133e56b1d58baf7ed44ce69dcb20f2f
|
@ -21,7 +21,7 @@
|
||||
+ (instancetype)get;
|
||||
|
||||
- (MPUserEntity *)activeUserForMainThread;
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc;
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context;
|
||||
- (void)setActiveUser:(MPUserEntity *)activeUser;
|
||||
|
||||
@end
|
||||
|
@ -28,18 +28,15 @@
|
||||
return [self activeUserInContext:[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady]];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc {
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
NSManagedObjectID *activeUserOID = self.activeUserOID;
|
||||
if (!activeUserOID || !moc)
|
||||
if (!activeUserOID || !context)
|
||||
return nil;
|
||||
|
||||
NSError *error;
|
||||
MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:activeUserOID error:&error];
|
||||
if (!activeUser) {
|
||||
MPUserEntity *activeUser = [MPUserEntity existingObjectWithID:activeUserOID inContext:context];
|
||||
if (!activeUser)
|
||||
[self signOutAnimated:YES];
|
||||
err(@"Failed to retrieve active user: %@", error);
|
||||
}
|
||||
|
||||
return activeUser;
|
||||
}
|
||||
|
@ -260,12 +260,9 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete) {
|
||||
NSManagedObjectID *elementOID = [self elementForTableIndexPath:indexPath].objectID;
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
NSError *error = nil;
|
||||
MPElementEntity *element = (MPElementEntity *)[context existingObjectWithID:elementOID error:&error];
|
||||
if (!element) {
|
||||
err(@"Failed to retrieve element to delete: %@", error);
|
||||
MPElementEntity *element = [MPElementEntity existingObjectWithID:elementOID inContext:context];
|
||||
if (!element)
|
||||
return;
|
||||
}
|
||||
|
||||
inf(@"Deleting element: %@", element.name);
|
||||
[context deleteObject:element];
|
||||
|
@ -18,10 +18,12 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPPasswordCell.h"
|
||||
#import "MPPasswordsViewController.h"
|
||||
|
||||
@interface MPPasswordElementCell : MPPasswordCell
|
||||
|
||||
@property(nonatomic, copy) NSString *transientSite;
|
||||
@property(nonatomic, weak) MPPasswordsViewController *passwordsViewController;
|
||||
|
||||
- (MPElementEntity *)mainElement;
|
||||
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context;
|
||||
|
@ -69,15 +69,7 @@
|
||||
|
||||
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
if (!_elementOID)
|
||||
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;
|
||||
return [MPElementEntity existingObjectWithID:_elementOID inContext:context];
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
|
@ -92,8 +92,17 @@
|
||||
|
||||
[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.typeLabel.text = [mainElement.algorithm nameOfType:self.type];
|
||||
if (self.type != (MPElementType)NSNotFound)
|
||||
self.typeLabel.text = [mainElement.algorithm nameOfType:self.type];
|
||||
|
||||
if (mainElement.requiresExplicitMigration)
|
||||
self.upgradeButton.alpha = 1;
|
||||
@ -166,6 +175,8 @@
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPElementEntity *element = [[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context];
|
||||
if (!element)
|
||||
return;
|
||||
|
||||
switch (self.contentFieldMode) {
|
||||
case MPContentFieldModePassword:
|
||||
|
@ -20,6 +20,7 @@
|
||||
#import "MPCell.h"
|
||||
#import "MPPasswordCell.h"
|
||||
#import "MPPasswordElementCell.h"
|
||||
#import "MPPasswordsViewController.h"
|
||||
|
||||
@interface MPPasswordTypesCell : MPPasswordElementCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||
|
||||
|
@ -68,9 +68,7 @@
|
||||
[super applyLayoutAttributes:layoutAttributes];
|
||||
|
||||
[self.contentCollectionView.collectionViewLayout invalidateLayout];
|
||||
if (self.activeType)
|
||||
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:self.activeType]
|
||||
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
|
||||
[self scrollToActiveType];
|
||||
}
|
||||
|
||||
- (void)reloadWithTransientSite:(NSString *)siteName {
|
||||
@ -104,7 +102,10 @@
|
||||
if (!self.algorithm)
|
||||
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 {
|
||||
@ -125,16 +126,13 @@
|
||||
|
||||
NSString *newSiteName = self.transientSite;
|
||||
if (newSiteName) {
|
||||
[[UIResponder findFirstResponder] resignFirstResponder];
|
||||
[PearlAlert showAlertWithTitle:@"Create Site"
|
||||
message:strf( @"Do you want to create a new site named:\n%@", newSiteName )
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex]) {
|
||||
// Cancel
|
||||
// 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;
|
||||
@ -142,70 +140,56 @@
|
||||
|
||||
// Create
|
||||
[[MPiOSAppDelegate get] addElementNamed:newSiteName completion:^(MPElementEntity *element) {
|
||||
[self copyContentOfElement:element];
|
||||
PearlMainQueue( ^{
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Added %@", newSiteName ) dismissAfter:2];
|
||||
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];
|
||||
} );
|
||||
[self.passwordsViewController updatePasswords];
|
||||
} );
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
return;
|
||||
}
|
||||
|
||||
MPElementEntity *element = [self mainElement];
|
||||
if (!element) {
|
||||
// [collectionView selectItemAtIndexPath:indexPath animated:NO
|
||||
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
|
||||
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
|
||||
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
|
||||
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
|
||||
return;
|
||||
}
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
BOOL used = NO;
|
||||
MPElementEntity *element = [self elementInContext:context];
|
||||
if (!element)
|
||||
wrn(@"No element to use for: %@", self);
|
||||
else if (indexPath.item == 0) {
|
||||
[context deleteObject:element];
|
||||
[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 );
|
||||
MPCheckpoint( MPCheckpointCopyToPasteboard, @{
|
||||
@"type" : NilToNSNull( element.typeName ),
|
||||
@"version" : @(element.version),
|
||||
@"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];
|
||||
@"type" : NilToNSNull( element.typeName ),
|
||||
@"version" : @(element.version),
|
||||
@"emergency" : @NO
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *result = [element resolveContentUsingKey:[MPAppDelegate_Shared get].key];
|
||||
if ([result length]) {
|
||||
[UIPasteboard generalPasteboard].string = result;
|
||||
PearlMainQueue( ^{
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2];
|
||||
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];
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2];
|
||||
return YES;
|
||||
}
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
[[self elementInContext:context] use];
|
||||
[context saveToStore];
|
||||
}];
|
||||
} );
|
||||
} );
|
||||
}];
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
@ -235,8 +219,18 @@
|
||||
|
||||
#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 {
|
||||
|
||||
if (self.transientSite)
|
||||
return [[self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN][indexPath.item] unsignedIntegerValue];
|
||||
|
||||
if (indexPath.item == 0)
|
||||
return (MPElementType)NSNotFound;
|
||||
|
||||
@ -247,20 +241,29 @@
|
||||
|
||||
NSArray *types = [self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN];
|
||||
for (NSInteger t = 0; t < [types count]; ++t)
|
||||
if ([types[t] unsignedIntegerValue] == type)
|
||||
return [NSIndexPath indexPathForItem:t + 1 inSection:0];
|
||||
if ([types[t] unsignedIntegerValue] == type) {
|
||||
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 {
|
||||
|
||||
if (self.transientSite)
|
||||
return;
|
||||
|
||||
CGPoint centerPoint = CGPointFromCGRectCenter( self.contentCollectionView.bounds );
|
||||
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) {
|
||||
MPPasswordLargeCell *cell = (MPPasswordLargeCell *)[self.contentCollectionView cellForItemAtIndexPath:centerIndexPath];
|
||||
@ -270,7 +273,7 @@
|
||||
}
|
||||
|
||||
MPElementEntity *element = [self elementInContext:context];
|
||||
if (element.type == cell.type)
|
||||
if (!element || element.type == cell.type)
|
||||
// Nothing changed.
|
||||
return;
|
||||
|
||||
@ -284,8 +287,7 @@
|
||||
|
||||
_activeType = activeType;
|
||||
|
||||
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:activeType]
|
||||
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
|
||||
[self scrollToActiveType];
|
||||
}
|
||||
|
||||
- (void)setSelected:(BOOL)selected {
|
||||
|
@ -36,6 +36,7 @@
|
||||
@property(nonatomic, readonly) MPCoachmark *coachmark;
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void (^)(BOOL finished))completion;
|
||||
- (void)updatePasswords;
|
||||
|
||||
- (IBAction)dismissPopdown:(id)sender;
|
||||
- (IBAction)signOut:(id)sender;
|
||||
|
@ -40,7 +40,6 @@
|
||||
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
|
||||
NSFetchedResultsController *_fetchedResultsController;
|
||||
BOOL _exactMatch;
|
||||
NSMutableDictionary *_fetchedUpdates;
|
||||
UIColor *_backgroundColor;
|
||||
UIColor *_darkenedBackgroundColor;
|
||||
__weak UIViewController *_popdownVC;
|
||||
@ -52,7 +51,6 @@
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
_fetchedUpdates = [NSMutableDictionary dictionaryWithCapacity:4];
|
||||
_backgroundColor = self.passwordCollectionView.backgroundColor;
|
||||
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
|
||||
_coachmark = [MPCoachmark coachmarkForClass:[self class] version:0];
|
||||
@ -118,11 +116,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
||||
if (collectionView == self.passwordCollectionView) {
|
||||
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
|
||||
CGFloat itemWidth = UIEdgeInsetsInsetRect(self.passwordCollectionView.bounds, layout.sectionInset).size.width;
|
||||
|
||||
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 );
|
||||
return CGSizeMake( itemWidth, 100 );
|
||||
}
|
||||
|
||||
Throw(@"Unexpected collection view: %@", collectionView);
|
||||
@ -155,14 +149,12 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
||||
MPPasswordElementCell *cell;
|
||||
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) {
|
||||
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
|
||||
if (indexPath.item < 3)
|
||||
cell = [MPPasswordTypesCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
|
||||
else
|
||||
cell = [MPPasswordSmallCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
|
||||
cell = [MPPasswordTypesCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
|
||||
}
|
||||
else
|
||||
// New Site.
|
||||
// New Site.
|
||||
cell = [MPPasswordTypesCell dequeueCellForTransientSite:self.query fromCollectionView:collectionView atIndexPath:indexPath];
|
||||
cell.passwordsViewController = self;
|
||||
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
dbg_return(cell, indexPath);
|
||||
@ -171,140 +163,30 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
||||
Throw(@"Unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
#pragma mark - NSFetchedResultsControllerDelegate
|
||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind
|
||||
atIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
|
||||
|
||||
if (controller == _fetchedResultsController) {
|
||||
dbg(@"controllerWillChangeContent");
|
||||
NSAssert(![_fetchedUpdates count], @"Didn't finish a previous change update?");
|
||||
if ([_fetchedUpdates count]) {
|
||||
[_fetchedUpdates removeAllObjects];
|
||||
[self.passwordCollectionView reloadData];
|
||||
}
|
||||
}
|
||||
return [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"MPPasswordHeader" forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
#pragma mark - NSFetchedResultsControllerDelegate
|
||||
|
||||
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
|
||||
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
|
||||
|
||||
if (controller == _fetchedResultsController) {
|
||||
NSMutableArray *updatesForType = _fetchedUpdates[@(type)];
|
||||
if (!updatesForType)
|
||||
_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;
|
||||
}
|
||||
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section]];
|
||||
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
|
||||
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
|
||||
|
||||
if (controller == _fetchedResultsController) {
|
||||
NSMutableArray *updatesForType = _fetchedUpdates[@(type << 3)];
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (controller == _fetchedResultsController)
|
||||
[self.passwordCollectionView reloadData];
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
|
||||
@ -319,13 +201,10 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
||||
|
||||
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
|
||||
|
||||
if (searchBar == self.passwordsSearchBar) {
|
||||
// _passwordsDismissRecognizer = [self.view dismissKeyboardForField:self.passwordsSearchBar onTouchForced:NO];
|
||||
|
||||
if (searchBar == self.passwordsSearchBar)
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.passwordCollectionView.backgroundColor = _darkenedBackgroundColor;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
|
||||
|
@ -360,28 +360,31 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||
// Delete User
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSManagedObject *user_ = [context existingObjectWithID:userID error:NULL];
|
||||
if (user_) {
|
||||
[context deleteObject:user_];
|
||||
[context saveToStore];
|
||||
}
|
||||
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
||||
if (!user_)
|
||||
return;
|
||||
|
||||
[context deleteObject:user_];
|
||||
[context saveToStore];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonIndex == [sheet firstOtherButtonIndex])
|
||||
// Reset Password
|
||||
// Reset Password
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *user_ = (MPUserEntity *)[context existingObjectWithID:userID error:NULL];
|
||||
if (user_)
|
||||
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
|
||||
PearlMainQueue( ^{
|
||||
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
|
||||
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
|
||||
scrollPosition:UICollectionViewScrollPositionNone];
|
||||
[self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath];
|
||||
} );
|
||||
}];
|
||||
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
||||
if (!user_)
|
||||
return;
|
||||
|
||||
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
|
||||
PearlMainQueue( ^{
|
||||
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
|
||||
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
|
||||
scrollPosition:UICollectionViewScrollPositionNone];
|
||||
[self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath];
|
||||
} );
|
||||
}];
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||
@ -477,12 +480,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
if ((*isNew = indexPath.item >= [self.userIDs count]))
|
||||
return nil;
|
||||
|
||||
NSError *error = nil;
|
||||
MPUserEntity *user = (MPUserEntity *)[context existingObjectWithID:self.userIDs[indexPath.item] error:&error];
|
||||
if (error)
|
||||
wrn(@"Failed to load user into context: %@", error);
|
||||
|
||||
return user;
|
||||
return [MPUserEntity existingObjectWithID:self.userIDs[indexPath.item] inContext:context];
|
||||
}
|
||||
|
||||
- (void)updateAvatars {
|
||||
|
@ -19,7 +19,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="504"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
@ -27,7 +27,7 @@
|
||||
<rect key="frame" x="142" y="234" width="37" height="37"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<constraints>
|
||||
@ -136,7 +136,7 @@
|
||||
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="lNu-mK-3zD"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<constraints>
|
||||
@ -150,7 +150,7 @@
|
||||
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="kL5-zV-zbb"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
@ -1307,7 +1307,7 @@
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</collectionReusableView>
|
||||
|
Loading…
Reference in New Issue
Block a user