Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07e55140ac | ||
|
|
fbbd08790d | ||
|
|
fcaa5d1d8c | ||
|
|
ea5be8efcb | ||
|
|
c8b4933c3d | ||
|
|
981ee171ae |
2
platform-darwin/External/Pearl
vendored
2
platform-darwin/External/Pearl
vendored
Submodule platform-darwin/External/Pearl updated: 5e5eaa1344...284cd041f8
@@ -47,7 +47,7 @@
|
|||||||
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D395105935859D71679931 /* MPOverlayViewController.m */; };
|
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D395105935859D71679931 /* MPOverlayViewController.m */; };
|
||||||
93D399E4BC1E092A8C8B12AE /* NSOrderedSetOrArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39FBF8FCEB4C106272334 /* NSOrderedSetOrArray.h */; };
|
93D399E4BC1E092A8C8B12AE /* NSOrderedSetOrArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39FBF8FCEB4C106272334 /* NSOrderedSetOrArray.h */; };
|
||||||
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */; };
|
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */; };
|
||||||
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; };
|
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadItems.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */; };
|
||||||
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; };
|
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; };
|
||||||
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
|
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
|
||||||
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */; };
|
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */; };
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; };
|
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; };
|
||||||
93D39BFB5F5F9337F6565DE3 /* UIView+Visible.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39B7B765546B1F1900CB7 /* UIView+Visible.h */; };
|
93D39BFB5F5F9337F6565DE3 /* UIView+Visible.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39B7B765546B1F1900CB7 /* UIView+Visible.h */; };
|
||||||
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
|
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
|
||||||
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */; };
|
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadItems.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadItems.m */; };
|
||||||
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; };
|
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; };
|
||||||
93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39CC01630D0421205C4C4 /* MPNavigationController.m */; };
|
93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39CC01630D0421205C4C4 /* MPNavigationController.m */; };
|
||||||
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
|
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
|
||||||
@@ -466,7 +466,7 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
93D390519405B76CC6A57C4F /* MPCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCell.h; sourceTree = "<group>"; };
|
93D390519405B76CC6A57C4F /* MPCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCell.h; sourceTree = "<group>"; };
|
||||||
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
|
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
|
||||||
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+PearlReloadFromArray.m"; sourceTree = "<group>"; };
|
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadItems.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+PearlReloadItems.m"; sourceTree = "<group>"; };
|
||||||
93D390A3B351FEF1B9EDAB56 /* mpw-algorithm_v2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v2.c"; sourceTree = "<group>"; };
|
93D390A3B351FEF1B9EDAB56 /* mpw-algorithm_v2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v2.c"; sourceTree = "<group>"; };
|
||||||
93D390A99850139D0FF0211E /* mpw-algorithm_v0.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v0.c"; sourceTree = "<group>"; };
|
93D390A99850139D0FF0211E /* mpw-algorithm_v0.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v0.c"; sourceTree = "<group>"; };
|
||||||
93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; };
|
93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; };
|
||||||
@@ -477,7 +477,7 @@
|
|||||||
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = "<group>"; };
|
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = "<group>"; };
|
||||||
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
|
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
|
||||||
93D391AA32F24290C424438E /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
|
93D391AA32F24290C424438E /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
|
||||||
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadFromArray.h"; sourceTree = "<group>"; };
|
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadItems.h"; sourceTree = "<group>"; };
|
||||||
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = "<group>"; };
|
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = "<group>"; };
|
||||||
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; };
|
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; };
|
||||||
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; };
|
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; };
|
||||||
@@ -3114,8 +3114,8 @@
|
|||||||
DAFE462515039823003ABA7C /* Resources */,
|
DAFE462515039823003ABA7C /* Resources */,
|
||||||
DA250A16195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.h */,
|
DA250A16195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.h */,
|
||||||
DA250A15195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m */,
|
DA250A15195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m */,
|
||||||
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */,
|
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */,
|
||||||
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */,
|
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadItems.m */,
|
||||||
DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */,
|
DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */,
|
||||||
DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */,
|
DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */,
|
||||||
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */,
|
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */,
|
||||||
@@ -3249,7 +3249,7 @@
|
|||||||
93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */,
|
93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */,
|
||||||
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */,
|
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */,
|
||||||
93D392A8777DC30C11361647 /* UITextView+PearlAttributes.h in Headers */,
|
93D392A8777DC30C11361647 /* UITextView+PearlAttributes.h in Headers */,
|
||||||
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */,
|
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadItems.h in Headers */,
|
||||||
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */,
|
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */,
|
||||||
93D399E4BC1E092A8C8B12AE /* NSOrderedSetOrArray.h in Headers */,
|
93D399E4BC1E092A8C8B12AE /* NSOrderedSetOrArray.h in Headers */,
|
||||||
93D3959696396A91961C6148 /* UIView+AlphaScale.h in Headers */,
|
93D3959696396A91961C6148 /* UIView+AlphaScale.h in Headers */,
|
||||||
@@ -3959,7 +3959,7 @@
|
|||||||
DAA141201922FF020032B392 /* PearlTween.m in Sources */,
|
DAA141201922FF020032B392 /* PearlTween.m in Sources */,
|
||||||
93D391ECBD9BD2C64115B5DD /* PearlSizedTextView.m in Sources */,
|
93D391ECBD9BD2C64115B5DD /* PearlSizedTextView.m in Sources */,
|
||||||
93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */,
|
93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */,
|
||||||
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */,
|
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadItems.m in Sources */,
|
||||||
93D3928D629EA563F9EC4909 /* NSPersistentStore+PearlMigration.m in Sources */,
|
93D3928D629EA563F9EC4909 /* NSPersistentStore+PearlMigration.m in Sources */,
|
||||||
93D392A33CCE85431E910C7B /* NSOrderedSetOrArray.m in Sources */,
|
93D392A33CCE85431E910C7B /* NSOrderedSetOrArray.m in Sources */,
|
||||||
93D393AA69A1193401160418 /* UIView+AlphaScale.m in Sources */,
|
93D393AA69A1193401160418 /* UIView+AlphaScale.m in Sources */,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
|
|||||||
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock;
|
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock;
|
||||||
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
||||||
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
||||||
|
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock;
|
||||||
|
|
||||||
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
||||||
- (void)deleteAndResetStore;
|
- (void)deleteAndResetStore;
|
||||||
|
|||||||
@@ -131,6 +131,25 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock {
|
||||||
|
|
||||||
|
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
|
||||||
|
if (!privateManagedObjectContextIfReady)
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
return PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, privateManagedObjectContextIfReady, nil,
|
||||||
|
^(id host, NSNotification *note) {
|
||||||
|
NSMutableDictionary *affectedObjects = [NSMutableDictionary new];
|
||||||
|
for (NSManagedObject *object in note.userInfo[NSInsertedObjectsKey])
|
||||||
|
affectedObjects[object.objectID] = NSInsertedObjectsKey;
|
||||||
|
for (NSManagedObject *object in note.userInfo[NSUpdatedObjectsKey])
|
||||||
|
affectedObjects[object.objectID] = NSUpdatedObjectsKey;
|
||||||
|
for (NSManagedObject *object in note.userInfo[NSDeletedObjectsKey])
|
||||||
|
affectedObjects[object.objectID] = NSDeletedObjectsKey;
|
||||||
|
changedBlock( affectedObjects );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
- (NSManagedObjectContext *)mainManagedObjectContextIfReady {
|
- (NSManagedObjectContext *)mainManagedObjectContextIfReady {
|
||||||
|
|
||||||
[self loadStore];
|
[self loadStore];
|
||||||
@@ -196,13 +215,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
|
|
||||||
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||||
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
|
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
|
||||||
|
if ([self.mainManagedObjectContext respondsToSelector:@selector( automaticallyMergesChangesFromParent )]) // iOS 10+
|
||||||
|
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
|
||||||
|
else
|
||||||
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
||||||
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
|
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
|
||||||
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
|
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainContext, NSNotification *note) {
|
||||||
[mainManagedObjectContext performBlock:^{
|
[mainContext performBlock:^{
|
||||||
@try {
|
@try {
|
||||||
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
|
[mainContext mergeChangesFromContextDidSaveNotification:note];
|
||||||
}
|
}
|
||||||
@catch (NSException *exception) {
|
@catch (NSException *exception) {
|
||||||
err( @"While merging changes:\n%@", [exception fullDescription] );
|
err( @"While merging changes:\n%@", [exception fullDescription] );
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
- (BOOL)saveToStore {
|
- (BOOL)saveToStore {
|
||||||
|
|
||||||
__block BOOL success = YES;
|
__block BOOL success = YES;
|
||||||
if ([self hasChanges]) {
|
if ([self hasChanges])
|
||||||
[self performBlockAndWait:^{
|
[self performBlockAndWait:^{
|
||||||
@try {
|
@try {
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
err( @"While saving: %@", [exception fullDescription] );
|
err( @"While saving: %@", [exception fullDescription] );
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
|
||||||
|
|
||||||
return success && (!self.parentContext || [self.parentContext saveToStore]);
|
return success && (!self.parentContext || [self.parentContext saveToStore]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -547,8 +547,8 @@
|
|||||||
|
|
||||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
|
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
|
||||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
|
fetchRequest.predicate =
|
||||||
queryPattern, queryPattern, [MPMacAppDelegate get].activeUserOID];
|
[NSPredicate predicateWithFormat:@"name LIKE[cd] %@ AND user == %@", queryPattern, [MPMacAppDelegate get].activeUserOID];
|
||||||
prof_rewind( @"fetchRequest" );
|
prof_rewind( @"fetchRequest" );
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
|
|||||||
@@ -161,10 +161,9 @@
|
|||||||
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||||
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||||
|
|
||||||
if ([cell isKindOfClass:[MPGlobalAnswersCell class]]) {
|
if ([cell isKindOfClass:[MPGlobalAnswersCell class]])
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
|
[self copyAnswer:((MPGlobalAnswersCell *)cell).answerField.text];
|
||||||
[UIPasteboard generalPasteboard].string = ((MPGlobalAnswersCell *)cell).answerField.text;
|
|
||||||
}
|
|
||||||
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
|
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
|
||||||
if (!_multiple)
|
if (!_multiple)
|
||||||
[self setMultiple:YES animated:YES];
|
[self setMultiple:YES animated:YES];
|
||||||
@@ -192,6 +191,7 @@
|
|||||||
} cancelTitle:@"Cancel" otherTitles:@"Remove Questions", nil];
|
} cancelTitle:@"Cancel" otherTitles:@"Remove Questions", nil];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
|
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
|
||||||
NSString *body;
|
NSString *body;
|
||||||
if (!_multiple) {
|
if (!_multiple) {
|
||||||
@@ -215,14 +215,30 @@
|
|||||||
|
|
||||||
[PearlEMail sendEMailTo:nil fromVC:self subject:strf( @"Master Password security answers for %@", site.name ) body:body];
|
[PearlEMail sendEMailTo:nil fromVC:self subject:strf( @"Master Password security answers for %@", site.name ) body:body];
|
||||||
}
|
}
|
||||||
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]]) {
|
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
|
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]])
|
||||||
[UIPasteboard generalPasteboard].string = ((MPAnswersQuestionCell *)cell).answerField.text;
|
[self copyAnswer:((MPAnswersQuestionCell *)cell).answerField.text];
|
||||||
}
|
|
||||||
|
|
||||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)copyAnswer:(NSString *)answer {
|
||||||
|
|
||||||
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||||
|
if ([pasteboard respondsToSelector:@selector( setItems:options: )]) {
|
||||||
|
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: answer } ]
|
||||||
|
options:@{
|
||||||
|
UIPasteboardOptionLocalOnly : @NO,
|
||||||
|
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
|
||||||
|
}];
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied (3 min)" ) dismissAfter:2];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pasteboard.string = answer;
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Private
|
#pragma mark - Private
|
||||||
|
|
||||||
- (void)updateAnimated:(BOOL)animated {
|
- (void)updateAnimated:(BOOL)animated {
|
||||||
|
|||||||
@@ -84,8 +84,19 @@
|
|||||||
- (IBAction)copyPassword:(id)sender {
|
- (IBAction)copyPassword:(id)sender {
|
||||||
|
|
||||||
NSString *sitePassword = [self.passwordButton titleForState:UIControlStateNormal];
|
NSString *sitePassword = [self.passwordButton titleForState:UIControlStateNormal];
|
||||||
if ([sitePassword length]) {
|
if (![sitePassword length])
|
||||||
[UIPasteboard generalPasteboard].string = sitePassword;
|
return;
|
||||||
|
|
||||||
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||||
|
if ([pasteboard respondsToSelector:@selector( setItems:options: )])
|
||||||
|
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: sitePassword } ]
|
||||||
|
options:@{
|
||||||
|
UIPasteboardOptionLocalOnly : @NO,
|
||||||
|
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
|
||||||
|
}];
|
||||||
|
else
|
||||||
|
pasteboard.string = sitePassword;
|
||||||
|
|
||||||
[UIView animateWithDuration:0.3f animations:^{
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
self.tipContainer.visible = YES;
|
self.tipContainer.visible = YES;
|
||||||
} completion:^(BOOL finished) {
|
} completion:^(BOOL finished) {
|
||||||
@@ -96,7 +107,6 @@
|
|||||||
} );
|
} );
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
#pragma mark - Private
|
||||||
|
|
||||||
|
|||||||
@@ -493,10 +493,12 @@
|
|||||||
|
|
||||||
- (void)updateAnimated:(BOOL)animated {
|
- (void)updateAnimated:(BOOL)animated {
|
||||||
|
|
||||||
|
Weakify( self );
|
||||||
if (![NSThread isMainThread]) {
|
if (![NSThread isMainThread]) {
|
||||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
PearlMainQueueOperation( ^{
|
||||||
|
Strongify( self );
|
||||||
[self updateAnimated:animated];
|
[self updateAnimated:animated];
|
||||||
}];
|
} );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,11 +546,17 @@
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
// Calculate Fields
|
// Calculate Fields
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPSiteEntity *site = [self siteInContext:context];
|
MPSiteEntity *site = [self siteInContext:context];
|
||||||
MPKey *key = [MPiOSAppDelegate get].key;
|
MPKey *key = [MPiOSAppDelegate get].key;
|
||||||
if (!key)
|
if (!key) {
|
||||||
|
wrn( @"Could not load cell content: key unavailable." );
|
||||||
|
PearlMainQueueOperation( ^{
|
||||||
|
Strongify( self );
|
||||||
|
[self updateAnimated:YES];
|
||||||
|
} );
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BOOL loginGenerated = site.loginGenerated;
|
BOOL loginGenerated = site.loginGenerated;
|
||||||
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
|
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
|
||||||
@@ -600,7 +608,13 @@
|
|||||||
else
|
else
|
||||||
self.indicatorView.hidden = YES;
|
self.indicatorView.hidden = YES;
|
||||||
} );
|
} );
|
||||||
}];
|
}]) {
|
||||||
|
wrn( @"Could not load cell content: store unavailable." );
|
||||||
|
PearlMainQueueOperation( ^{
|
||||||
|
Strongify( self );
|
||||||
|
[self updateAnimated:YES];
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
[self.contentView layoutIfNeeded];
|
[self.contentView layoutIfNeeded];
|
||||||
}];
|
}];
|
||||||
@@ -635,8 +649,21 @@
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
|
[self.window endEditing:YES];
|
||||||
|
|
||||||
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||||
|
if ([pasteboard respondsToSelector:@selector(setItems:options:)]) {
|
||||||
|
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: password } ]
|
||||||
|
options:@{
|
||||||
|
UIPasteboardOptionLocalOnly : @NO,
|
||||||
|
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
|
||||||
|
}];
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Password Copied (3 min)" ) dismissAfter:2];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pasteboard.string = password;
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Password Copied" ) dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Password Copied" ) dismissAfter:2];
|
||||||
[UIPasteboard generalPasteboard].string = password;
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
[site use];
|
[site use];
|
||||||
@@ -652,8 +679,10 @@
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
|
[self.window endEditing:YES];
|
||||||
|
|
||||||
[UIPasteboard generalPasteboard].string = loginName;
|
[UIPasteboard generalPasteboard].string = loginName;
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
|
||||||
} );
|
} );
|
||||||
|
|
||||||
[site use];
|
[site use];
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
@property(assign, nonatomic) BOOL active;
|
@property(assign, nonatomic) BOOL active;
|
||||||
|
|
||||||
- (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;
|
- (void)reloadPasswords;
|
||||||
|
|
||||||
- (IBAction)dismissPopdown:(id)sender;
|
- (IBAction)dismissPopdown:(id)sender;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
#import "MPAnswersViewController.h"
|
#import "MPAnswersViewController.h"
|
||||||
#import "MPMessageViewController.h"
|
#import "MPMessageViewController.h"
|
||||||
|
|
||||||
|
static const NSString *MPTransientPasswordItem = @"MPTransientPasswordItem";
|
||||||
|
|
||||||
typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||||
MPPasswordsBadNameTip = 1 << 0,
|
MPPasswordsBadNameTip = 1 << 0,
|
||||||
};
|
};
|
||||||
@@ -32,7 +34,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
|
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
|
||||||
|
|
||||||
@property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar;
|
@property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar;
|
||||||
@property(nonatomic, readonly) NSString *query;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -42,11 +43,9 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
UIColor *_backgroundColor;
|
UIColor *_backgroundColor;
|
||||||
UIColor *_darkenedBackgroundColor;
|
UIColor *_darkenedBackgroundColor;
|
||||||
__weak UIViewController *_popdownVC;
|
__weak UIViewController *_popdownVC;
|
||||||
BOOL _showTransientItem;
|
|
||||||
NSUInteger _transientItem;
|
|
||||||
NSCharacterSet *_siteNameAcceptableCharactersSet;
|
NSCharacterSet *_siteNameAcceptableCharactersSet;
|
||||||
NSArray *_fuzzyGroups;
|
NSArray *_fuzzyGroups;
|
||||||
NSMutableArray *_passwordCollectionViewUpdatesBatch;
|
NSMutableArray<NSMutableArray *> *_passwordCollectionSections;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Life
|
#pragma mark - Life
|
||||||
@@ -62,8 +61,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
|
|
||||||
_backgroundColor = self.passwordCollectionView.backgroundColor;
|
_backgroundColor = self.passwordCollectionView.backgroundColor;
|
||||||
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
|
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
|
||||||
_transientItem = NSNotFound;
|
_passwordCollectionSections = [NSMutableArray new];
|
||||||
_passwordCollectionViewUpdatesBatch = [NSMutableArray arrayWithCapacity:4];
|
|
||||||
|
|
||||||
self.view.backgroundColor = [UIColor clearColor];
|
self.view.backgroundColor = [UIColor clearColor];
|
||||||
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
|
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
|
||||||
@@ -83,7 +81,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
|
|
||||||
[self registerObservers];
|
[self registerObservers];
|
||||||
[self updateConfigKey:nil];
|
[self updateConfigKey:nil];
|
||||||
[self updatePasswords];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated {
|
- (void)viewDidAppear:(BOOL)animated {
|
||||||
@@ -140,30 +137,42 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
return CGSizeMake( itemWidth, 100 );
|
return CGSizeMake( itemWidth, 100 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
|
||||||
|
insetForSectionAtIndex:(NSInteger)section {
|
||||||
|
|
||||||
|
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
|
||||||
|
UIEdgeInsets occludedInsets = [self.passwordCollectionView occludedInsets];
|
||||||
|
UIEdgeInsets insets = layout.sectionInset;
|
||||||
|
|
||||||
|
if (section == 0)
|
||||||
|
insets.top += occludedInsets.top;
|
||||||
|
|
||||||
|
if (section == collectionView.numberOfSections - 1)
|
||||||
|
insets.bottom += occludedInsets.bottom;
|
||||||
|
|
||||||
|
return insets;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - UICollectionViewDataSource
|
#pragma mark - UICollectionViewDataSource
|
||||||
|
|
||||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
||||||
|
|
||||||
return [self.fetchedResultsController.sections count];
|
return [_passwordCollectionSections count];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||||
|
|
||||||
if (![MPiOSAppDelegate get].activeUserOID || !_fetchedResultsController)
|
return [_passwordCollectionSections[(NSUInteger)section] count];
|
||||||
return 0;
|
|
||||||
|
|
||||||
NSUInteger objects = ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[section]).numberOfObjects;
|
|
||||||
_transientItem = _showTransientItem? objects: NSNotFound;
|
|
||||||
return objects + (_showTransientItem? 1: 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
|
MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
|
||||||
[cell setFuzzyGroups:_fuzzyGroups];
|
[cell setFuzzyGroups:_fuzzyGroups];
|
||||||
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
|
id item = _passwordCollectionSections[(NSUInteger)indexPath.section][(NSUInteger)indexPath.item];
|
||||||
[cell setSite:[self.fetchedResultsController objectAtIndexPath:indexPath] animated:NO];
|
if ([item isKindOfClass:[MPSiteEntity class]])
|
||||||
else
|
[cell setSite:item animated:NO];
|
||||||
|
else // item == MPTransientPasswordItem
|
||||||
[cell setTransientSite:self.query animated:NO];
|
[cell setTransientSite:self.query animated:NO];
|
||||||
|
|
||||||
return cell;
|
return cell;
|
||||||
@@ -180,61 +189,14 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
|
|
||||||
#pragma mark - NSFetchedResultsControllerDelegate
|
#pragma mark - NSFetchedResultsControllerDelegate
|
||||||
|
|
||||||
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
|
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
|
||||||
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
|
|
||||||
|
|
||||||
Weakify( self );
|
|
||||||
|
|
||||||
if (controller == _fetchedResultsController) {
|
|
||||||
@synchronized (_passwordCollectionViewUpdatesBatch) {
|
|
||||||
[_passwordCollectionViewUpdatesBatch addObject:[^{
|
|
||||||
Strongify( self );
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case NSFetchedResultsChangeInsert:
|
|
||||||
[self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
|
|
||||||
break;
|
|
||||||
case NSFetchedResultsChangeDelete:
|
|
||||||
[self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]];
|
|
||||||
break;
|
|
||||||
case NSFetchedResultsChangeMove:
|
|
||||||
if (![indexPath isEqual:newIndexPath])
|
|
||||||
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
|
|
||||||
break;
|
|
||||||
case NSFetchedResultsChangeUpdate:
|
|
||||||
[self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} copy]];
|
|
||||||
}
|
|
||||||
|
|
||||||
[controller.managedObjectContext performBlock:^{
|
|
||||||
PearlMainQueueOperation( ^{
|
|
||||||
@try {
|
|
||||||
[self.passwordCollectionView performBatchUpdates:^{
|
|
||||||
[self updateTransientItem];
|
|
||||||
|
|
||||||
@synchronized (_passwordCollectionViewUpdatesBatch) {
|
|
||||||
for (VoidBlock block in _passwordCollectionViewUpdatesBatch)
|
|
||||||
block();
|
|
||||||
[_passwordCollectionViewUpdatesBatch removeAllObjects];
|
|
||||||
}
|
|
||||||
} completion:nil];
|
|
||||||
}
|
|
||||||
@catch (NSException *exception) {
|
|
||||||
wrn( @"While updating password cells: %@", [exception fullDescription] );
|
|
||||||
[self.passwordCollectionView reloadData];
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
|
|
||||||
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
|
|
||||||
|
|
||||||
if (controller == _fetchedResultsController)
|
if (controller == _fetchedResultsController)
|
||||||
[self.passwordCollectionView reloadData];
|
PearlMainQueue( ^{
|
||||||
|
[self.passwordCollectionView updateDataSource:_passwordCollectionSections
|
||||||
|
toSections:[self createPasswordCollectionSections]
|
||||||
|
reloadItems:nil completion:nil];
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - UISearchBarDelegate
|
#pragma mark - UISearchBarDelegate
|
||||||
@@ -266,7 +228,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
if (_passwordsDismissRecognizer)
|
if (_passwordsDismissRecognizer)
|
||||||
[self.view removeGestureRecognizer:_passwordsDismissRecognizer];
|
[self.view removeGestureRecognizer:_passwordsDismissRecognizer];
|
||||||
|
|
||||||
[self updatePasswords];
|
[self reloadPasswords];
|
||||||
[UIView animateWithDuration:0.3f animations:^{
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
self.passwordCollectionView.backgroundColor = _backgroundColor;
|
self.passwordCollectionView.backgroundColor = _backgroundColor;
|
||||||
}];
|
}];
|
||||||
@@ -290,7 +252,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
if ([[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length])
|
if ([[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length])
|
||||||
[self showTips:MPPasswordsBadNameTip];
|
[self showTips:MPPasswordsBadNameTip];
|
||||||
|
|
||||||
[self updatePasswords];
|
[self reloadPasswords];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,39 +273,58 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateTransientItem {
|
- (NSMutableArray<NSMutableArray *> *)createPasswordCollectionSections {
|
||||||
|
|
||||||
NSString *query = self.query;
|
NSString *query = self.query;
|
||||||
_showTransientItem = [query length] > 0 && ![[[self.fetchedResultsController.sections[0] objects] filteredArrayUsingPredicate:
|
BOOL needTransientItem = [query length] > 0;
|
||||||
[NSPredicate predicateWithBlock:^BOOL(MPSiteEntity *site, NSDictionary<NSString *, id> *bindings) {
|
|
||||||
return [site.name isEqualToString:query];
|
NSArray<id<NSFetchedResultsSectionInfo>> *sectionInfos = [self.fetchedResultsController sections];
|
||||||
}]] count];
|
NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:[sectionInfos count]];
|
||||||
if (!_showTransientItem && _transientItem != NSNotFound)
|
for (id<NSFetchedResultsSectionInfo> sectionInfo in sectionInfos) {
|
||||||
[self.passwordCollectionView deleteItemsAtIndexPaths:
|
NSArray<MPSiteEntity *> *sites = [sectionInfo.objects copy];
|
||||||
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
|
[sections addObject:sites];
|
||||||
else if (_showTransientItem && _transientItem == NSNotFound) {
|
|
||||||
NSUInteger objects = [self.fetchedResultsController.sections[0] numberOfObjects];
|
if (needTransientItem)
|
||||||
[self.passwordCollectionView insertItemsAtIndexPaths:
|
for (MPSiteEntity *site in sites)
|
||||||
@[ [NSIndexPath indexPathForItem:objects inSection:0] ]];
|
if ([site.name isEqualToString:query]) {
|
||||||
|
needTransientItem = NO;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (_transientItem != NSNotFound)
|
}
|
||||||
[self.passwordCollectionView reloadItemsAtIndexPaths:
|
|
||||||
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
|
if (needTransientItem)
|
||||||
|
[sections addObject:@[ MPTransientPasswordItem ]];
|
||||||
|
|
||||||
|
return sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)registerObservers {
|
- (void)registerObservers {
|
||||||
|
|
||||||
|
static NSRegularExpression *bareHostRE = nil;
|
||||||
|
static dispatch_once_t once = 0;
|
||||||
|
dispatch_once( &once, ^{
|
||||||
|
bareHostRE = [NSRegularExpression regularExpressionWithPattern:@"([^\\.]+\\.[^\\.]+)$" options:0 error:nil];
|
||||||
|
} );
|
||||||
|
|
||||||
PearlRemoveNotificationObservers();
|
PearlRemoveNotificationObservers();
|
||||||
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
|
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
self.passwordSelectionContainer.visible = NO;
|
self.passwordSelectionContainer.visible = NO;
|
||||||
} );
|
} );
|
||||||
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
|
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
|
||||||
[self updatePasswords];
|
|
||||||
} );
|
|
||||||
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
|
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
|
NSURL *pasteboardURL = nil;
|
||||||
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||||
|
if ([pasteboard respondsToSelector:@selector( hasURLs )])
|
||||||
|
pasteboardURL = pasteboard.hasURLs? pasteboard.URL: nil;
|
||||||
|
else
|
||||||
|
pasteboardURL = [NSURL URLWithString:pasteboard.string];
|
||||||
|
|
||||||
|
if (pasteboardURL.host)
|
||||||
|
self.query = NSNullToNil( [pasteboardURL.host firstMatchGroupsOfExpression:bareHostRE][0] );
|
||||||
|
else
|
||||||
|
[self reloadPasswords];
|
||||||
|
|
||||||
[UIView animateWithDuration:0.7f animations:^{
|
[UIView animateWithDuration:0.7f animations:^{
|
||||||
self.passwordSelectionContainer.visible = YES;
|
self.passwordSelectionContainer.visible = YES;
|
||||||
}];
|
}];
|
||||||
@@ -351,9 +332,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
PearlAddNotificationObserver( MPSignedOutNotification, nil, nil,
|
PearlAddNotificationObserver( MPSignedOutNotification, nil, nil,
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
_fetchedResultsController = nil;
|
self->_fetchedResultsController = nil;
|
||||||
self.passwordsSearchBar.text = nil;
|
self.query = nil;
|
||||||
[self.passwordCollectionView reloadData];
|
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
PearlAddNotificationObserver( MPCheckConfigNotification, nil, nil,
|
PearlAddNotificationObserver( MPCheckConfigNotification, nil, nil,
|
||||||
@@ -365,26 +345,23 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
self->_fetchedResultsController = nil;
|
self->_fetchedResultsController = nil;
|
||||||
PearlMainQueue( ^{
|
[self reloadPasswords];
|
||||||
[self.passwordCollectionView reloadData];
|
|
||||||
} );
|
|
||||||
} );
|
} );
|
||||||
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
[self updatePasswords];
|
[self reloadPasswords];
|
||||||
[self registerObservers];
|
[self registerObservers];
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
[MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
|
||||||
if (mainContext)
|
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) {
|
||||||
PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil,
|
|
||||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
|
||||||
// TODO: either move this into the app delegate or remove the duplicate signOutAnimated: call from the app delegate.
|
// TODO: either move this into the app delegate or remove the duplicate signOutAnimated: call from the app delegate.
|
||||||
if (![[MPiOSAppDelegate get] activeUserInContext:note.object])
|
if (![[MPiOSAppDelegate get] activeUserInContext:mainContext])
|
||||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||||
} );
|
}];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateConfigKey:(NSString *)key {
|
- (void)updateConfigKey:(NSString *)key {
|
||||||
@@ -395,18 +372,9 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
[self.passwordCollectionView reloadData];
|
[self.passwordCollectionView reloadData];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updatePasswords {
|
- (void)reloadPasswords {
|
||||||
|
|
||||||
NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID;
|
|
||||||
if (!activeUserOID) {
|
|
||||||
PearlMainQueue( ^{
|
|
||||||
self.passwordsSearchBar.text = nil;
|
|
||||||
[self.passwordCollectionView reloadData];
|
|
||||||
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) animated:YES];
|
|
||||||
} );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||||
static NSRegularExpression *fuzzyRE;
|
static NSRegularExpression *fuzzyRE;
|
||||||
static dispatch_once_t once = 0;
|
static dispatch_once_t once = 0;
|
||||||
dispatch_once( &once, ^{
|
dispatch_once( &once, ^{
|
||||||
@@ -414,62 +382,28 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
NSString *queryString = self.query;
|
NSString *queryString = self.query;
|
||||||
NSString *queryPattern;
|
NSString *queryPattern = [[queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1"]
|
||||||
if ([queryString length] < 13)
|
stringByAppendingString:@"*"];
|
||||||
queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
|
NSMutableArray *fuzzyGroups = [NSMutableArray new];
|
||||||
else
|
|
||||||
// If query is too long, a wildcard per character makes the CoreData fetch take excessively long.
|
|
||||||
queryPattern = strf( @"*%@*", queryString );
|
|
||||||
NSMutableArray *fuzzyGroups = [NSMutableArray arrayWithCapacity:[queryString length]];
|
|
||||||
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
|
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
|
||||||
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||||
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
|
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
|
||||||
}];
|
}];
|
||||||
_fuzzyGroups = fuzzyGroups;
|
_fuzzyGroups = fuzzyGroups;
|
||||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
|
||||||
NSArray *oldSectionInfos = [self.fetchedResultsController sections];
|
|
||||||
NSMutableArray *oldSections = [[NSMutableArray alloc] initWithCapacity:[oldSectionInfos count]];
|
|
||||||
for (id<NSFetchedResultsSectionInfo> sectionInfo in oldSectionInfos)
|
|
||||||
[oldSections addObject:[sectionInfo.objects copy]];
|
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
self.fetchedResultsController.fetchRequest.predicate =
|
self.fetchedResultsController.fetchRequest.predicate =
|
||||||
[NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
|
[NSPredicate predicateWithFormat:@"name LIKE[cd] %@ AND user == %@", queryPattern, [MPiOSAppDelegate get].activeUserOID];
|
||||||
queryPattern, queryPattern, activeUserOID];
|
|
||||||
if (![self.fetchedResultsController performFetch:&error])
|
if (![self.fetchedResultsController performFetch:&error])
|
||||||
err( @"Couldn't fetch sites: %@", [error fullDescription] );
|
err( @"Couldn't fetch sites: %@", [error fullDescription] );
|
||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
@try {
|
[self.passwordCollectionView updateDataSource:_passwordCollectionSections
|
||||||
[self.passwordCollectionView performBatchUpdates:^{
|
toSections:[self createPasswordCollectionSections]
|
||||||
[self updateTransientItem];
|
reloadItems:@[ MPTransientPasswordItem ] completion:^(BOOL finished) {
|
||||||
|
|
||||||
NSInteger fromSections = self.passwordCollectionView.numberOfSections;
|
|
||||||
NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView];
|
|
||||||
for (NSInteger section = 0; section < MAX( toSections, fromSections ); ++section) {
|
|
||||||
if (section >= fromSections)
|
|
||||||
[self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
|
|
||||||
else if (section >= toSections)
|
|
||||||
[self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
|
|
||||||
else if (section < [oldSections count])
|
|
||||||
[self.passwordCollectionView reloadItemsFromArray:oldSections[section]
|
|
||||||
toArray:[[self.fetchedResultsController sections][section] objects]
|
|
||||||
inSection:section];
|
|
||||||
else
|
|
||||||
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:section]];
|
|
||||||
}
|
|
||||||
} completion:^(BOOL finished) {
|
|
||||||
if (finished)
|
|
||||||
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top )
|
|
||||||
animated:YES];
|
|
||||||
for (MPPasswordCell *cell in self.passwordCollectionView.visibleCells)
|
for (MPPasswordCell *cell in self.passwordCollectionView.visibleCells)
|
||||||
[cell setFuzzyGroups:_fuzzyGroups];
|
[cell setFuzzyGroups:_fuzzyGroups];
|
||||||
}];
|
}];
|
||||||
}
|
|
||||||
@catch (NSException *exception) {
|
|
||||||
wrn( @"While updating password cells: %@", [exception fullDescription] );
|
|
||||||
[self.passwordCollectionView reloadData];
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -481,18 +415,24 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
|||||||
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setQuery:(NSString *)query {
|
||||||
|
|
||||||
|
self.passwordsSearchBar.text = [query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
|
[self reloadPasswords];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSFetchedResultsController *)fetchedResultsController {
|
- (NSFetchedResultsController *)fetchedResultsController {
|
||||||
|
|
||||||
if (!_fetchedResultsController) {
|
if (!_fetchedResultsController) {
|
||||||
_showTransientItem = NO;
|
|
||||||
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
|
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
|
||||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
fetchRequest.sortDescriptors = @[
|
fetchRequest.sortDescriptors = @[
|
||||||
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
|
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
|
||||||
];
|
];
|
||||||
fetchRequest.fetchBatchSize = 10;
|
fetchRequest.fetchBatchSize = 10;
|
||||||
_fetchedResultsController = [[NSFetchedResultsController alloc]
|
_fetchedResultsController =
|
||||||
initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
|
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:mainContext
|
||||||
|
sectionNameKeyPath:nil cacheName:nil];
|
||||||
_fetchedResultsController.delegate = self;
|
_fetchedResultsController.delegate = self;
|
||||||
}];
|
}];
|
||||||
[self registerObservers];
|
[self registerObservers];
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
|
|||||||
|
|
||||||
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
|
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
|
||||||
|
|
||||||
[self reloadCellsHiding:self.allCellsBySection[0] showing:@[ self.loadingCell ]];
|
[self updateCellsHiding:self.allCellsBySection[0] showing:@[ self.loadingCell ]];
|
||||||
[self.allCellsBySection[0] enumerateObjectsUsingBlock:^(MPStoreProductCell *cell, NSUInteger idx, BOOL *stop) {
|
[self.allCellsBySection[0] enumerateObjectsUsingBlock:^(MPStoreProductCell *cell, NSUInteger idx, BOOL *stop) {
|
||||||
if ([cell isKindOfClass:[MPStoreProductCell class]]) {
|
if ([cell isKindOfClass:[MPStoreProductCell class]]) {
|
||||||
cell.purchasedIndicator.visible = NO;
|
cell.purchasedIndicator.visible = NO;
|
||||||
@@ -257,7 +257,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
|
|||||||
}
|
}
|
||||||
|
|
||||||
[hideCells removeObjectsInArray:showCells];
|
[hideCells removeObjectsInArray:showCells];
|
||||||
[self reloadCellsHiding:hideCells showing:showCells];
|
[self updateCellsHiding:hideCells showing:showCells];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateFuel {
|
- (void)updateFuel {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
|||||||
@implementation MPUsersViewController {
|
@implementation MPUsersViewController {
|
||||||
NSString *_masterPasswordChoice;
|
NSString *_masterPasswordChoice;
|
||||||
NSOperationQueue *_afterUpdates;
|
NSOperationQueue *_afterUpdates;
|
||||||
|
__weak id _contextChangedObserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidLoad {
|
- (void)viewDidLoad {
|
||||||
@@ -90,15 +91,6 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
|||||||
self.userSelectionContainer.visible = NO;
|
self.userSelectionContainer.visible = NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillDisappear:(BOOL)animated {
|
|
||||||
|
|
||||||
[super viewWillDisappear:animated];
|
|
||||||
|
|
||||||
PearlRemoveNotificationObservers();
|
|
||||||
|
|
||||||
[self.marqueeTipTimer invalidate];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated {
|
- (void)viewDidAppear:(BOOL)animated {
|
||||||
|
|
||||||
[super viewDidAppear:animated];
|
[super viewDidAppear:animated];
|
||||||
@@ -130,6 +122,15 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
|||||||
((MPWebViewController *)segue.destinationViewController).initialURL = [NSURL URLWithString:@"http://thanks.lhunath.com"];
|
((MPWebViewController *)segue.destinationViewController).initialURL = [NSURL URLWithString:@"http://thanks.lhunath.com"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
|
[super viewWillDisappear:animated];
|
||||||
|
|
||||||
|
[self removeObservers];
|
||||||
|
|
||||||
|
[self.marqueeTipTimer invalidate];
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - UITextFieldDelegate
|
#pragma mark - UITextFieldDelegate
|
||||||
|
|
||||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||||
@@ -465,25 +466,23 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
- (void)deleteUser:(NSManagedObjectID *)userID {
|
- (void)deleteUser:(NSManagedObjectID *)userID {
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPUserEntity
|
MPUserEntity *user = [MPUserEntity existingObjectWithID:userID inContext:context];
|
||||||
*user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
if (!user)
|
||||||
if (!user_)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[context deleteObject:user_];
|
[context deleteObject:user];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resetUser:(NSManagedObjectID *)userID avatar:(MPAvatarCell *)avatarCell {
|
- (void)resetUser:(NSManagedObjectID *)userID avatar:(MPAvatarCell *)avatarCell {
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
MPUserEntity *user = [MPUserEntity existingObjectWithID:userID inContext:context];
|
||||||
if (!user_)
|
if (!user)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
|
[[MPiOSAppDelegate get] changeMasterPasswordFor:user saveInContext:context didResetBlock:^{
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
|
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
|
||||||
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
|
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
|
||||||
@@ -645,15 +644,21 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)registerObservers {
|
- (void)removeObservers {
|
||||||
|
|
||||||
[self removeKeyPathObservers];
|
[self removeKeyPathObservers];
|
||||||
|
PearlRemoveNotificationObservers();
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:_contextChangedObserver];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)registerObservers {
|
||||||
|
|
||||||
|
[self removeObservers];
|
||||||
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
|
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
|
||||||
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
|
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
|
||||||
[_self updateAvatarVisibility];
|
[_self updateAvatarVisibility];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
PearlRemoveNotificationObservers();
|
|
||||||
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
|
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
|
||||||
^(MPUsersViewController *self, NSNotification *note) {
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
self.userSelectionContainer.visible = NO;
|
self.userSelectionContainer.visible = NO;
|
||||||
@@ -675,37 +680,32 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
|
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
|
||||||
} );
|
} );
|
||||||
|
|
||||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
if ((_contextChangedObserver = [MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
|
||||||
[UIView animateWithDuration:0.3f animations:^{
|
if ([[[affectedObjects allKeys] filteredArrayUsingPredicate:
|
||||||
self.avatarCollectionView.visible = mainContext != nil;
|
[NSPredicate predicateWithBlock:^BOOL(NSManagedObjectID *objectID, NSDictionary *bindings) {
|
||||||
}];
|
return [objectID.entity.name isEqualToString:NSStringFromClass( [MPUserEntity class] )];
|
||||||
if (mainContext && self.storeLoadingActivity.isAnimating)
|
|
||||||
[self.storeLoadingActivity stopAnimating];
|
|
||||||
if (!mainContext && !self.storeLoadingActivity.isAnimating)
|
|
||||||
[self.storeLoadingActivity startAnimating];
|
|
||||||
|
|
||||||
if (mainContext)
|
|
||||||
PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, mainContext, nil,
|
|
||||||
^(MPUsersViewController *self, NSNotification *note) {
|
|
||||||
NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey];
|
|
||||||
NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey];
|
|
||||||
if ([[NSSetUnion( insertedObjects, deletedObjects )
|
|
||||||
filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
|
|
||||||
return [evaluatedObject isKindOfClass:[MPUserEntity class]];
|
|
||||||
}]] count])
|
}]] count])
|
||||||
[self reloadUsers];
|
[self reloadUsers];
|
||||||
} );
|
}]))
|
||||||
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
|
self.avatarCollectionView.visible = YES;
|
||||||
|
[self.storeLoadingActivity stopAnimating];
|
||||||
|
}];
|
||||||
|
else
|
||||||
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
|
self.avatarCollectionView.visible = NO;
|
||||||
|
[self.storeLoadingActivity startAnimating];
|
||||||
|
}];
|
||||||
|
|
||||||
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
|
||||||
^(MPUsersViewController *self, NSNotification *note) {
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
self.userIDs = nil;
|
self.userIDs = nil;
|
||||||
} );
|
} );
|
||||||
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
|
||||||
^(MPUsersViewController *self, NSNotification *note) {
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
PearlMainQueue( ^{
|
|
||||||
[self registerObservers];
|
[self registerObservers];
|
||||||
[self reloadUsers];
|
[self reloadUsers];
|
||||||
} );
|
} );
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reloadUsers {
|
- (void)reloadUsers {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
@property(nonatomic, strong) UIDocumentInteractionController *interactionController;
|
@property(nonatomic, strong) UIDocumentInteractionController *interactionController;
|
||||||
|
|
||||||
|
@property(nonatomic) UIBackgroundTaskIdentifier task;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPiOSAppDelegate
|
@implementation MPiOSAppDelegate
|
||||||
@@ -244,6 +245,44 @@
|
|||||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||||
[self signOutAnimated:NO];
|
[self signOutAnimated:NO];
|
||||||
|
|
||||||
|
// self.task = [application beginBackgroundTaskWithExpirationHandler:^{
|
||||||
|
// [application endBackgroundTask:self.task];
|
||||||
|
// dbg( @"background expiring" );
|
||||||
|
// }];
|
||||||
|
// PearlNotMainQueueOperation( ^{
|
||||||
|
// NSString *pbstring = [UIPasteboard generalPasteboard].string;
|
||||||
|
// while (YES) {
|
||||||
|
// NSString *newString = [UIPasteboard generalPasteboard].string;
|
||||||
|
// if (![newString isEqualToString:pbstring]) {
|
||||||
|
// dbg( @"pasteboard changed to: %@", newString );
|
||||||
|
// pbstring = newString;
|
||||||
|
// NSURL *url = [NSURL URLWithString:pbstring];
|
||||||
|
// if (url) {
|
||||||
|
// NSString *siteName = [url host];
|
||||||
|
// }
|
||||||
|
// MPKey *key = [MPiOSAppDelegate get].key;
|
||||||
|
// if (key)
|
||||||
|
// [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
|
// NSFetchRequest<MPSiteEntity *>
|
||||||
|
// *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
|
// fetchRequest.sortDescriptors = @[
|
||||||
|
// [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
|
||||||
|
// ];
|
||||||
|
// fetchRequest.fetchBatchSize = 2;
|
||||||
|
// fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(name LIKE[cd] %@) AND user == %@", siteName,
|
||||||
|
// [[MPiOSAppDelegate get] activeUserOID]];
|
||||||
|
// NSError *error = nil;
|
||||||
|
// NSArray<MPSiteEntity *> *results = [fetchRequest execute:&error];
|
||||||
|
// dbg( @"site search, error: %@, results:\n%@", error, results );
|
||||||
|
// if ([results count]) {
|
||||||
|
// [UIPasteboard generalPasteboard].string = [[results firstObject] resolvePasswordUsingKey:key];
|
||||||
|
// }
|
||||||
|
// }];
|
||||||
|
// }
|
||||||
|
// [NSThread sleepForTimeInterval:5];
|
||||||
|
// }
|
||||||
|
// } );
|
||||||
|
|
||||||
[super applicationDidEnterBackground:application];
|
[super applicationDidEnterBackground:application];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -526,7 +526,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" navigationBarHidden="YES" id="Q1S-vU-GGO" customClass="MPNavigationController" sceneMemberID="viewController">
|
<navigationController definesPresentationContext="YES" navigationBarHidden="YES" id="Q1S-vU-GGO" customClass="MPNavigationController" sceneMemberID="viewController">
|
||||||
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="lightContent"/>
|
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="lightContent"/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="4yl-zs-iUd">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" id="4yl-zs-iUd">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
@@ -1155,7 +1155,7 @@
|
|||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="K2e-Gh-7hH" userLabel="Passwords Container">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="K2e-Gh-7hH" userLabel="Passwords Container">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" minimumZoomScale="0.0" maximumZoomScale="0.0" keyboardDismissMode="onDrag" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="aXw-tn-8Sj" userLabel="Password Collection">
|
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="aXw-tn-8Sj" userLabel="Password Collection">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<inset key="scrollIndicatorInsets" minX="0.0" minY="108" maxX="0.0" maxY="0.0"/>
|
<inset key="scrollIndicatorInsets" minX="0.0" minY="108" maxX="0.0" maxY="0.0"/>
|
||||||
@@ -1163,11 +1163,11 @@
|
|||||||
<size key="itemSize" width="355" height="100"/>
|
<size key="itemSize" width="355" height="100"/>
|
||||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||||
<inset key="sectionInset" minX="10" minY="118" maxX="10" maxY="10"/>
|
<inset key="sectionInset" minX="10" minY="10" maxX="10" maxY="10"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPPasswordCell" id="W2g-yv-V3V" customClass="MPPasswordCell">
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPPasswordCell" id="W2g-yv-V3V" customClass="MPPasswordCell">
|
||||||
<rect key="frame" x="10" y="118" width="355" height="100"/>
|
<rect key="frame" x="10" y="10" width="355" height="100"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
|
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
|
||||||
@@ -2022,7 +2022,7 @@ eg. apple.com, rmitchell@twitter.com</string>
|
|||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="LBn-EA-NAH" sceneMemberID="viewController">
|
<navigationController definesPresentationContext="YES" id="LBn-EA-NAH" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="Settings" image="icon_gears.png" id="6n3-Ay-Knn"/>
|
<tabBarItem key="tabBarItem" title="Settings" image="icon_gears.png" id="6n3-Ay-Knn"/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="h6j-1o-LHf">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" id="h6j-1o-LHf">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
@@ -2273,7 +2273,7 @@ Suspendisse potenti. Etiam ut nisi id augue tempor ultrices et sit amet sapien.
|
|||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="Ec4-G7-5Td" customClass="PearlNavigationController" sceneMemberID="viewController">
|
<navigationController definesPresentationContext="YES" id="Ec4-G7-5Td" customClass="PearlNavigationController" sceneMemberID="viewController">
|
||||||
<extendedEdge key="edgesForExtendedLayout"/>
|
<extendedEdge key="edgesForExtendedLayout"/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="MA4-cY-VCZ">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" barStyle="black" id="MA4-cY-VCZ">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<color key="tintColor" red="0.47450980390000003" green="0.86666666670000003" blue="0.98431372549999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="0.47450980390000003" green="0.86666666670000003" blue="0.98431372549999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
mpw-2.5-cli-1-0-gb01e370f.tar.gz
|
mpw-2.5-cli-2-0-g3ed6b937.tar.gz
|
||||||
@@ -1 +1 @@
|
|||||||
mpw-2.5-cli-1-0-gb01e370f.tar.gz.sig
|
mpw-2.5-cli-2-0-g3ed6b937.tar.gz.sig
|
||||||
BIN
public/site/2013-05/mpw-2.5-cli-2-0-g3ed6b937.tar.gz
Normal file
BIN
public/site/2013-05/mpw-2.5-cli-2-0-g3ed6b937.tar.gz
Normal file
Binary file not shown.
BIN
public/site/2013-05/mpw-2.5-cli-2-0-g3ed6b937.tar.gz.sig
Normal file
BIN
public/site/2013-05/mpw-2.5-cli-2-0-g3ed6b937.tar.gz.sig
Normal file
Binary file not shown.
Reference in New Issue
Block a user