diff --git a/Crashlytics/Crashlytics.framework/Versions/A/Crashlytics b/Crashlytics/Crashlytics.framework/Versions/A/Crashlytics index d03754f4..1f7dd198 100644 Binary files a/Crashlytics/Crashlytics.framework/Versions/A/Crashlytics and b/Crashlytics/Crashlytics.framework/Versions/A/Crashlytics differ diff --git a/Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist b/Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist index e2adff2a..b535b5cb 100644 Binary files a/Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist and b/Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist differ diff --git a/External/iCloudStoreManager b/External/iCloudStoreManager index 8c7894a9..20e2a1de 160000 --- a/External/iCloudStoreManager +++ b/External/iCloudStoreManager @@ -1 +1 @@ -Subproject commit 8c7894a983f1c1e0795a6e3d5ee7a223a5e01a54 +Subproject commit 20e2a1dec08fef1e99433f9a8b3a2310bb00717d diff --git a/MasterPassword-Mac.xcodeproj/project.pbxproj b/MasterPassword-Mac.xcodeproj/project.pbxproj index 81da1981..f80027bb 100644 --- a/MasterPassword-Mac.xcodeproj/project.pbxproj +++ b/MasterPassword-Mac.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 93D39C24C655D0FD48DCA1C3 /* menu-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 93D39E6D20434649192464E0 /* menu-icon.png */; }; + 93D39DEE2F9E55DAA206FD6E /* menu-icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 93D39F808A2C83E35C5C0411 /* menu-icon@2x.png */; }; DA34F0CC163B8C31006FFC95 /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = DA34F08D163B8C31006FFC95 /* NSArray+Indexing.h */; }; DA34F0CD163B8C31006FFC95 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = DA34F08E163B8C31006FFC95 /* NSArray+Indexing.m */; }; DA34F0CE163B8C31006FFC95 /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA34F08F163B8C31006FFC95 /* NSBundle+PearlMutableInfo.h */; }; @@ -662,6 +664,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 93D39E6D20434649192464E0 /* menu-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu-icon.png"; sourceTree = ""; }; + 93D39F808A2C83E35C5C0411 /* menu-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu-icon@2x.png"; sourceTree = ""; }; DA34F08D163B8C31006FFC95 /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = ""; }; DA34F08E163B8C31006FFC95 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = ""; }; DA34F08F163B8C31006FFC95 /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = ""; }; @@ -2565,6 +2569,8 @@ DA600E9615057F10008E9AB6 /* Lock */, DA600EA015057F10008E9AB6 /* logo-bare.png */, DA600EA115057F10008E9AB6 /* Tooltips */, + 93D39E6D20434649192464E0 /* menu-icon.png */, + 93D39F808A2C83E35C5C0411 /* menu-icon@2x.png */, ); path = Resources; sourceTree = ""; @@ -5719,6 +5725,8 @@ DAF236BA163B24D5008AF5B5 /* MainMenu.xib in Resources */, DAF236BD163B24D5008AF5B5 /* MasterPassword.entitlements in Resources */, DAF236C0163B24D5008AF5B5 /* MPPasswordWindowController.xib in Resources */, + 93D39C24C655D0FD48DCA1C3 /* menu-icon.png in Resources */, + 93D39DEE2F9E55DAA206FD6E /* menu-icon@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5935,7 +5943,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.8; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -5994,7 +6002,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.8; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SDKROOT = macosx; diff --git a/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword.xcscheme b/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword.xcscheme index 1e7fa13b..f72579b2 100644 --- a/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword.xcscheme +++ b/MasterPassword-Mac.xcodeproj/xcshareddata/xcschemes/MasterPassword.xcscheme @@ -56,12 +56,6 @@ ReferencedContainer = "container:MasterPassword-Mac.xcodeproj"> - - - - diff --git a/MasterPassword.sketch/Data b/MasterPassword.sketch/Data index da1a919b..b658590b 100644 Binary files a/MasterPassword.sketch/Data and b/MasterPassword.sketch/Data differ diff --git a/MasterPassword.sketch/QuickLook/Preview.png b/MasterPassword.sketch/QuickLook/Preview.png index d91d6ac7..021b56ba 100644 Binary files a/MasterPassword.sketch/QuickLook/Preview.png and b/MasterPassword.sketch/QuickLook/Preview.png differ diff --git a/MasterPassword.sketch/QuickLook/Thumbnail.png b/MasterPassword.sketch/QuickLook/Thumbnail.png index b50fb03d..827c9c66 100644 Binary files a/MasterPassword.sketch/QuickLook/Thumbnail.png and b/MasterPassword.sketch/QuickLook/Thumbnail.png differ diff --git a/MasterPassword/MPAlgorithm.h b/MasterPassword/MPAlgorithm.h index ad085770..abcd2fd8 100644 --- a/MasterPassword/MPAlgorithm.h +++ b/MasterPassword/MPAlgorithm.h @@ -25,6 +25,7 @@ @required - (NSUInteger)version; +- (void)migrateUser:(MPUserEntity *)user completion:(void(^)(BOOL userRequiresNewMigration))completion; - (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit; - (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName; diff --git a/MasterPassword/MPAlgorithmV0.m b/MasterPassword/MPAlgorithmV0.m index 015c3b8a..85667bcc 100644 --- a/MasterPassword/MPAlgorithmV0.m +++ b/MasterPassword/MPAlgorithmV0.m @@ -31,6 +31,31 @@ return 0; } +- (void)migrateUser:(MPUserEntity *)user completion:(void(^)(BOOL userRequiresNewMigration))completion { + + BOOL didRequireExplicitMigration = user.requiresExplicitMigration; + [user.managedObjectContext performBlock:^void() { + NSError *error = nil; + NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; + migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d", MPAlgorithmDefaultVersion]; + NSArray *migrationElements = [user.managedObjectContext executeFetchRequest:migrationRequest error:&error]; + if (!migrationElements) { + err(@"While looking for elements to migrate: %@", error); + return; + } + + if (didRequireExplicitMigration) + user.requiresExplicitMigration = NO; + for (MPElementEntity *migrationElement in migrationElements) + if (![migrationElement migrateExplicitly:NO]) + user.requiresExplicitMigration = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + completion(!didRequireExplicitMigration && user.requiresExplicitMigration); + }); + }]; +} + - (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit { if (element.version != [self version] - 1) diff --git a/MasterPassword/MPAppDelegate_Shared.h b/MasterPassword/MPAppDelegate_Shared.h index f55d90a4..055cffdb 100644 --- a/MasterPassword/MPAppDelegate_Shared.h +++ b/MasterPassword/MPAppDelegate_Shared.h @@ -20,6 +20,4 @@ + (MPAppDelegate_Shared *)get; -- (NSURL *)applicationFilesDirectory; - @end diff --git a/MasterPassword/MPAppDelegate_Shared.m b/MasterPassword/MPAppDelegate_Shared.m index 2acb232e..643389a2 100644 --- a/MasterPassword/MPAppDelegate_Shared.m +++ b/MasterPassword/MPAppDelegate_Shared.m @@ -28,23 +28,6 @@ #endif } -- (NSURL *)applicationFilesDirectory { - -#if TARGET_OS_IPHONE - return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; -#else - NSURL *appSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject]; - NSURL *applicationFilesDirectory = [appSupportURL URLByAppendingPathComponent:@"com.lyndir.lhunath.MasterPassword"]; - - NSError *error = nil; - [[NSFileManager defaultManager] createDirectoryAtURL:applicationFilesDirectory withIntermediateDirectories:YES attributes:nil error:&error]; - if (error) - err(@"Couldn't create application directory: %@, error occurred: %@", applicationFilesDirectory, error); - - return applicationFilesDirectory; -#endif -} - - (MPUserEntity *)activeUser { if (!self.activeUserID) diff --git a/MasterPassword/MPAppDelegate_Store.h b/MasterPassword/MPAppDelegate_Store.h index 1767cca8..e1cce87f 100644 --- a/MasterPassword/MPAppDelegate_Store.h +++ b/MasterPassword/MPAppDelegate_Store.h @@ -21,9 +21,7 @@ typedef enum { @interface MPAppDelegate_Shared (Store) + (NSManagedObjectContext *)managedObjectContextIfReady; -+ (NSManagedObjectModel *)managedObjectModel; - (NSManagedObjectContext *)managedObjectContextIfReady; -- (NSManagedObjectModel *)managedObjectModel; - (UbiquityStoreManager *)storeManager; - (void)saveContext; diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m index 143c47f6..718f08ab 100644 --- a/MasterPassword/MPAppDelegate_Store.m +++ b/MasterPassword/MPAppDelegate_Store.m @@ -6,10 +6,15 @@ // Copyright (c) 2011 Lyndir. All rights reserved. // +#import #import "MPAppDelegate_Store.h" +#import +#import @implementation MPAppDelegate_Shared (Store) +static char managedObjectContextKey; + #pragma mark - Core Data setup + (NSManagedObjectContext *)managedObjectContextIfReady { @@ -17,37 +22,21 @@ return [[self get] managedObjectContextIfReady]; } -+ (NSManagedObjectModel *)managedObjectModel { - - return [[self get] managedObjectModel]; -} - -- (NSManagedObjectModel *)managedObjectModel { - - static NSManagedObjectModel *managedObjectModel = nil; - if (managedObjectModel) - return managedObjectModel; - - return managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil]; -} - - (NSManagedObjectContext *)managedObjectContextIfReady { - if (![self storeManager].isReady) - return nil; - - static NSManagedObjectContext *managedObjectContext = nil; + NSManagedObjectContext *managedObjectContext = objc_getAssociatedObject(self, &managedObjectContextKey); if (!managedObjectContext) { managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [managedObjectContext performBlockAndWait:^{ managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; - managedObjectContext.undoManager = [NSUndoManager new]; managedObjectContext.persistentStoreCoordinator = self.storeManager.persistentStoreCoordinator; }]; + + objc_setAssociatedObject(self, &managedObjectContextKey, managedObjectContext, OBJC_ASSOCIATION_RETAIN); } - [[self storeManager] persistentStoreCoordinator]; - if (![self storeManager].isReady) + if (![managedObjectContext.persistentStoreCoordinator.persistentStores count]) + // Store not available yet. return nil; return managedObjectContext; @@ -59,34 +48,86 @@ if (storeManager) return storeManager; - storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel] - localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"] - containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared" + storeManager = [[UbiquityStoreManager alloc] initStoreNamed:nil withManagedObjectModel:nil localStoreURL:nil + containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared" #if TARGET_OS_IPHONE - additionalStoreOptions:@{ - NSPersistentStoreFileProtectionKey: NSFileProtectionComplete - } + additionalStoreOptions:@{ + NSPersistentStoreFileProtectionKey : NSFileProtectionComplete + }]; #else - additionalStoreOptions:nil + additionalStoreOptions:nil]; #endif - ]; storeManager.delegate = self; -#ifdef DEBUG - storeManager.hardResetEnabled = YES; -#endif -#if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification - object:[UIApplication sharedApplication] queue:nil + + // Migrate old store to new store location. + NSNumber *cloudEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:@"iCloudEnabledKey"]; + if (cloudEnabled) { + if ([cloudEnabled boolValue]) { + NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"]; + NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"]; + NSURL *oldCloudContentURL = [[cloudContainerURL URLByAppendingPathComponent:@"Data" isDirectory:YES] + URLByAppendingPathComponent:uuid isDirectory:YES]; + NSURL *oldCloudStoreURL = [[[cloudContainerURL URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES] + URLByAppendingPathComponent:uuid isDirectory:NO] + URLByAppendingPathExtension:@"sqlite"]; + NSURL *newCloudContentURL = [storeManager URLForCloudContent]; + NSURL *newCloudStoreURL = [storeManager URLForCloudStore]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:oldCloudStoreURL.path isDirectory:NO] && + ![[NSFileManager defaultManager] fileExistsAtPath:newCloudStoreURL.path isDirectory:NO]) { + NSError *error = nil; + NSDictionary *options = @{ + NSPersistentStoreUbiquitousContentNameKey : uuid, + NSPersistentStoreUbiquitousContentURLKey : oldCloudContentURL, + NSMigratePersistentStoresAutomaticallyOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES}; + NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; + NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; + NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil + URL:oldCloudStoreURL options:options error:&error]; + if (oldStore) + [psc migratePersistentStore:oldStore toURL:newCloudStoreURL options:options withType:NSSQLiteStoreType error:&error]; + if (error) + err(@"While migrating cloud store from %@ -> %@: %@", oldCloudStoreURL, newCloudStoreURL, error); + else { + [psc removePersistentStore:[psc.persistentStores lastObject] error:nil]; + [[NSFileManager defaultManager] removeItemAtURL:oldCloudStoreURL error:nil]; + } + } + } else { + NSURL *applicationFilesDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory + inDomains:NSUserDomainMask] lastObject]; + NSURL *oldLocalStoreURL = [[applicationFilesDirectory URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] + URLByAppendingPathExtension:@"sqlite"]; + NSURL *newLocalStoreURL = [storeManager URLForLocalStore]; + if ([[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NO] && + ![[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) { + NSError *error = nil; + NSDictionary *options = @{ + NSMigratePersistentStoresAutomaticallyOption : @YES, + NSInferMappingModelAutomaticallyOption : @YES}; + NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; + NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; + NSPersistentStore *oldStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil + URL:oldLocalStoreURL options:options error:&error]; + if (oldStore) + [psc migratePersistentStore:oldStore toURL:newLocalStoreURL options:options withType:NSSQLiteStoreType error:&error]; + if (error) + err(@"While migrating local store from %@ -> %@: %@", oldLocalStoreURL, newLocalStoreURL, error); + else { + [psc removePersistentStore:[psc.persistentStores lastObject] error:nil]; + [[NSFileManager defaultManager] removeItemAtURL:oldLocalStoreURL error:nil]; + } + } + } + } + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"iCloudEnabledKey"]; + + [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification + object:storeManager queue:nil usingBlock:^(NSNotification *note) { - [storeManager checkiCloudStatus]; + objc_setAssociatedObject(self, &managedObjectContextKey, nil, OBJC_ASSOCIATION_RETAIN); }]; -#else - [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification - object:[NSApplication sharedApplication] queue:nil - usingBlock:^(NSNotification *note) { - [storeManager checkiCloudStatus]; - }]; -#endif #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication] queue:nil @@ -128,16 +169,16 @@ - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - // manager.iCloudEnabled is more reliable (eg. iOS' MPAppDelegate tampers with didSwitch a bit) - iCloudEnabled = manager.iCloudEnabled; + // manager.cloudEnabled is more reliable (eg. iOS' MPAppDelegate tampers with didSwitch a bit) + iCloudEnabled = manager.cloudEnabled; inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO"); #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled]; + [TestFlight passCheckpoint:cloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled]; #endif #ifdef LOCALYTICS [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud attributes:@{ - @"enabled": iCloudEnabled? @"YES": @"NO" + @"enabled": cloudEnabled? @"YES": @"NO" }]; #endif @@ -162,7 +203,6 @@ switch (cause) { case UbiquityStoreManagerErrorCauseDeleteStore: - case UbiquityStoreManagerErrorCauseDeleteLogs: case UbiquityStoreManagerErrorCauseCreateStorePath: case UbiquityStoreManagerErrorCauseClearStore: break; @@ -178,13 +218,12 @@ #ifdef LOCALYTICS [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreReset attributes:nil]; #endif - manager.hardResetEnabled = YES; - [manager hardResetLocalStorage]; + [manager deleteLocalStore]; Throw(@"Local store was reset, application must be restarted to use it."); } else // Try again. - [[self storeManager] persistentStoreCoordinator]; + [manager persistentStoreCoordinator]; } case UbiquityStoreManagerErrorCauseOpenCloudStore: { wrn(@"iCloud store could not be opened: %@", error); @@ -198,13 +237,17 @@ #ifdef LOCALYTICS [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreReset attributes:nil]; #endif - manager.hardResetEnabled = YES; - [manager hardResetCloudStorage]; + [manager deleteCloudStore]; break; } else // Try again. - [[self storeManager] persistentStoreCoordinator]; + [manager persistentStoreCoordinator]; } + case UbiquityStoreManagerErrorCauseMigrateLocalToCloudStore: { + wrn(@"Couldn't migrate local store to the cloud: %@", error); + wrn(@"Resetting the iCloud store."); + [manager deleteCloudStore]; + }; } } diff --git a/MasterPassword/Mac/MPAppDelegate.m b/MasterPassword/Mac/MPAppDelegate.m index 71a2470e..0466c2af 100644 --- a/MasterPassword/Mac/MPAppDelegate.m +++ b/MasterPassword/Mac/MPAppDelegate.m @@ -9,8 +9,6 @@ #import "MPAppDelegate.h" #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" -#import "MPConfig.h" -#import "MPElementEntity.h" #import @@ -29,15 +27,19 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfour-char-constants" static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1}; +static EventHotKeyID MPLockHotKey = {.signature = 'lock', .id = 1}; #pragma clang diagnostic pop + (void)initialize { - [MPMacConfig get]; + static dispatch_once_t initialize; + dispatch_once(&initialize, ^{ + [MPMacConfig get]; -#ifdef DEBUG - [PearlLogger get].printLevel = PearlLogLevelTrace; -#endif + #ifdef DEBUG + [PearlLogger get].printLevel = PearlLogLevelTrace; + #endif + }); } + (MPAppDelegate *)get { @@ -57,6 +59,10 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [((__bridge MPAppDelegate *)userData) activate:nil]; return noErr; } + if (hotKeyID.signature == MPLockHotKey.signature && hotKeyID.id == MPLockHotKey.id) { + [((__bridge MPAppDelegate *)userData) signOut:nil]; + return noErr; + } return eventNotHandledErr; } @@ -65,19 +71,21 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [[[self.usersItem submenu] itemArray] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if (idx > 1) - [[self.usersItem submenu] removeItemAtIndex:(NSInteger)idx]; + [[self.usersItem submenu] removeItem:obj]; }]; NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextIfReady]; if (!moc) { - [self.createUserItem setEnabled:NO]; + self.createUserItem.title = @"New User (Not ready)"; + self.createUserItem.enabled = NO; self.createUserItem.toolTip = @"Please wait until the app is fully loaded."; - [[self.usersItem.submenu addItemWithTitle:@"Loading..." action:NULL keyEquivalent:@""] setEnabled:NO]; - + [self.usersItem.submenu addItemWithTitle:@"Loading..." action:NULL keyEquivalent:@""].enabled = NO; + return; } - [self.createUserItem setEnabled:YES]; + self.createUserItem.title = @"New User"; + self.createUserItem.enabled = YES; self.createUserItem.toolTip = nil; [moc performBlockAndWait:^{ @@ -88,6 +96,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven users = [moc executeFetchRequest:fetchRequest error:&error]; if (!users) err(@"Failed to load users: %@", error); + + if (![users count]) { + NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""]; + noUsersItem.enabled = NO; + noUsersItem.toolTip = @"Use the iOS app to create users and make sure iCloud is enabled in its preferences as well. " + @"Then give iCloud some time to sync the new user to your Mac."; + } for (MPUserEntity *user in users) { NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector(selectUser:) keyEquivalent:@""]; @@ -110,18 +125,17 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (void)showMenu { - self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState; - self.savePasswordItem.state = [MPAppDelegate get].activeUser.saveKey? NSOnState: NSOffState; - if (!(self.showItem.enabled = ![self.passwordWindow.window isVisible])) - self.showItem.toolTip = @"Master Password is already showing."; - else - self.showItem.toolTip = nil; + [self updateMenuItems]; [self.statusItem popUpStatusItemMenu:self.statusMenu]; } - (IBAction)activate:(id)sender { + if (!self.activeUser) + // No user, can't activate. + return; + if ([[NSApplication sharedApplication] isActive]) [self applicationDidBecomeActive:nil]; else @@ -131,7 +145,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (IBAction)togglePreference:(NSMenuItem *)sender { if (sender == useICloudItem) - [self.storeManager useiCloudStore:sender.state == NSOffState alertUser:YES]; + self.storeManager.cloudEnabled = (sender.state == NSOnState); if (sender == rememberPasswordItem) [MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]]; if (sender == savePasswordItem) { @@ -165,34 +179,24 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven // Setup delegates and listeners. [MPConfig get].delegate = self; + __weak id weakSelf = self; [self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { - if (self.key) { - [self.lockItem setEnabled:YES]; - self.lockItem.toolTip = nil; - - [self.savePasswordItem setEnabled:YES]; - self.savePasswordItem.toolTip = nil; - } else { - [self.lockItem setEnabled:NO]; - self.lockItem.toolTip = @"Master Password is currently locked."; - - [self.savePasswordItem setEnabled:NO]; - self.savePasswordItem.toolTip = @"First unlock by selecting your user and showing the Master Password window."; - - [self.passwordWindow close]; - } + [weakSelf updateMenuItems]; } forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil]; + [self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { + [weakSelf updateMenuItems]; + } forKeyPath:@"activeUser" options:NSKeyValueObservingOptionInitial context:nil]; // Initially, use iCloud. if ([[MPConfig get].firstRun boolValue]) - [[self storeManager] useiCloudStore:YES alertUser:YES]; + [self storeManager].cloudEnabled = YES; // Status item. - self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; - self.statusItem.title = @"•••"; - self.statusItem.highlightMode = YES; - self.statusItem.target = self; - self.statusItem.action = @selector(showMenu); + self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; + self.statusItem.image = [NSImage imageNamed:@"menu-icon"]; + self.statusItem.highlightMode = YES; + self.statusItem.target = self; + self.statusItem.action = @selector(showMenu); [self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { [[[self.usersItem submenu] itemArray] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { @@ -204,14 +208,15 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [MPMacConfig get].usedUserName = self.activeUser.name; } forKeyPath:@"activeUser" options:0 context:nil]; - [[NSNotificationCenter defaultCenter] addObserverForName:PersistentStoreDidChange object:nil queue:nil usingBlock: - ^(NSNotification *note) { - [self updateUsers]; - }]; - [[NSNotificationCenter defaultCenter] addObserverForName:PersistentStoreDidMergeChanges object:nil queue:nil usingBlock: + [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidChangeNotification object:nil queue:nil usingBlock: ^(NSNotification *note) { [self updateUsers]; }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UbiquityManagedStoreDidImportChangesNotification object:nil queue:nil + usingBlock: + ^(NSNotification *note) { + [self updateUsers]; + }]; [self updateUsers]; // Global hotkey. @@ -224,7 +229,61 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven err(@"Error installing application event handler: %d", status); status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef); if (status != noErr) - err(@"Error registering hotkey: %d", status); + err(@"Error registering 'show' hotkey: %d", status); + status = RegisterEventHotKey(35 /* p */, controlKey + optionKey + cmdKey, MPLockHotKey, GetApplicationEventTarget(), 0, &hotKeyRef); + if (status != noErr) + err(@"Error registering 'lock' hotkey: %d", status); +} + +- (void)updateMenuItems { + + if (!(self.showItem.enabled = ![self.passwordWindow.window isVisible])) { + self.showItem.title = @"Show (Showing)"; + self.showItem.toolTip = @"Master Password is already showing."; + } else if (!(self.showItem.enabled = (self.activeUser != nil))) { + self.showItem.title = @"Show (No user)"; + self.showItem.toolTip = @"First select the user to show passwords for."; + } else { + self.showItem.title = @"Show"; + self.showItem.toolTip = nil; + } + + if (self.key) { + self.lockItem.title = @"Lock"; + self.lockItem.enabled = YES; + self.lockItem.toolTip = nil; + } else { + self.lockItem.title = @"Lock (Locked)"; + self.lockItem.enabled = NO; + self.lockItem.toolTip = @"Master Password is currently locked."; + } + + self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState; + + self.savePasswordItem.state = [MPAppDelegate get].activeUser.saveKey? NSOnState: NSOffState; + if (!self.activeUser) { + self.savePasswordItem.title = @"Save Password (No user)"; + self.savePasswordItem.enabled = NO; + self.savePasswordItem.toolTip = @"First select your user and unlock by showing the Master Password window."; + } else if (!self.key) { + self.savePasswordItem.title = @"Save Password (Locked)"; + self.savePasswordItem.enabled = NO; + self.savePasswordItem.toolTip = @"First unlock by showing the Master Password window."; + } else { + self.savePasswordItem.title = @"Save Password"; + self.savePasswordItem.enabled = YES; + self.savePasswordItem.toolTip = nil; + } + + self.useICloudItem.state = [[MPMacConfig get].iCloud boolValue]? NSOnState: NSOffState; + if (!(self.useICloudItem.enabled = ![[MPMacConfig get].iCloud boolValue])) { + self.useICloudItem.title = @"Use iCloud (Required)"; + self.useICloudItem.toolTip = @"iCloud is required in this version. Future versions will work without iCloud as well."; + } + else { + self.useICloudItem.title = @"Use iCloud (Required)"; + self.useICloudItem.toolTip = nil; + } } - (void)applicationWillBecomeActive:(NSNotification *)notification { @@ -295,12 +354,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [super ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; - self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState; - if (!(self.useICloudItem.enabled = !iCloudEnabled)) - self.useICloudItem.toolTip = @"iCloud is required in this version. Future versions will work without iCloud as well."; - else - self.useICloudItem.toolTip = nil; - + [self updateMenuItems]; + if (![[MPConfig get].iCloudDecided boolValue]) { if (iCloudEnabled) return; @@ -310,7 +365,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven informativeTextWithFormat:@"It is highly recommended you enable iCloud."] runModal]) { case NSAlertDefaultReturn: { [MPConfig get].iCloudDecided = @YES; - [manager useiCloudStore:YES alertUser:NO]; + manager.cloudEnabled = YES; break; } diff --git a/MasterPassword/Mac/MPPasswordWindowController.h b/MasterPassword/Mac/MPPasswordWindowController.h index 669cef87..e91b0399 100644 --- a/MasterPassword/Mac/MPPasswordWindowController.h +++ b/MasterPassword/Mac/MPPasswordWindowController.h @@ -20,6 +20,7 @@ @property (nonatomic, weak) IBOutlet NSTextField *tipField; @property (nonatomic, weak) IBOutlet NSView *contentContainer; @property (nonatomic, weak) IBOutlet NSProgressIndicator *progressView; +@property (nonatomic, weak) IBOutlet NSTextField *userLabel; - (void)unlock; diff --git a/MasterPassword/Mac/MPPasswordWindowController.m b/MasterPassword/Mac/MPPasswordWindowController.m index 8c5ed679..09d60ea1 100644 --- a/MasterPassword/Mac/MPPasswordWindowController.m +++ b/MasterPassword/Mac/MPPasswordWindowController.m @@ -10,8 +10,6 @@ #import "MPAppDelegate.h" #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" -#import "MPElementEntity.h" -#import "MPElementGeneratedEntity.h" @interface MPPasswordWindowController () @@ -35,6 +33,19 @@ [self setContent:@""]; [self.tipField setStringValue:@""]; + [[MPAppDelegate get] addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { + [self.userLabel setStringValue:PearlString(@"%@'s password for:", [MPAppDelegate get].activeUser.name)]; + } forKeyPath:@"activeUser" options:NSKeyValueObservingOptionInitial context:nil]; + [[MPAppDelegate get] addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { + if ([MPAppDelegate get].activeUser && [MPAppDelegate get].key) + [MPAlgorithmDefault migrateUser:[MPAppDelegate get].activeUser completion:^(BOOL userRequiresNewMigration) { + if (userRequiresNewMigration) + [NSAlert alertWithMessageText:@"Migration Needed" defaultButton:@"OK" alternateButton:nil otherButton:nil + informativeTextWithFormat:@"Certain sites require explicit migration to get updated to the latest version of the " + @"Master Password algorithm. For these sites, a migration button will appear. Migrating these sites will cause " + @"their passwords to change. You'll need to update your profile for that site with the new password."]; + }]; + } forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil usingBlock:^(NSNotification *note) { if (!self.inProgress) @@ -57,6 +68,10 @@ if (shouldComplete) [[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil]; }]; + [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil + usingBlock:^(NSNotification *note) { + [self.window close]; + }]; [super windowDidLoad]; } @@ -98,8 +113,11 @@ @"If you log in with a different master password, your current sites will be unavailable.\n\n" @"You can always change back to your current master password later.\n" @"Your current sites and passwords will then become available again."] runModal] - == 1) + == 1) { + [MPAppDelegate get].activeUser.keyID = nil; [[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser]; + [[MPAppDelegate get] signOutAnimated:YES]; + } break; case NSAlertOtherReturn: @@ -126,6 +144,9 @@ }); }); } + + default: + break; } } @@ -164,8 +185,8 @@ [[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; if ([[NSPasteboard generalPasteboard] setString:self.content forType:NSPasteboardTypeString]) { self.tipField.alphaValue = 1; - [self.tipField setStringValue:@"Copied!"]; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)); + [self.tipField setStringValue:@"Copied! Hit ⎋ (ESC) to close window."]; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^{ [NSAnimationContext beginGrouping]; [[NSAnimationContext currentContext] setDuration:0.2f]; @@ -197,18 +218,12 @@ _content = content; - NSShadow *shadow = [NSShadow new]; - shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f]; - shadow.shadowOffset = NSMakeSize(1.0f, -1.0f); - shadow.shadowBlurRadius = 1.2f; - NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new]; paragraph.alignment = NSCenterTextAlignment; [self.contentField setAttributedStringValue: [[NSAttributedString alloc] initWithString:_content attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys: - shadow, NSShadowAttributeName, paragraph, NSParagraphStyleAttributeName, nil]]]; } @@ -222,6 +237,8 @@ return NO; } + dbg(@"element:\n%@", [result debugDescription]); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSString *description = [result.content description]; if (!description) @@ -229,7 +246,7 @@ dispatch_async(dispatch_get_main_queue(), ^{ [self setContent:description]; - [self.tipField setStringValue:@"Hit enter to copy the password."]; + [self.tipField setStringValue:@"Hit ⌤ (ENTER) to copy the password."]; self.tipField.alphaValue = 1; }); }); diff --git a/MasterPassword/Mac/MPPasswordWindowController.xib b/MasterPassword/Mac/MPPasswordWindowController.xib index 830d52b3..20bbb96a 100644 --- a/MasterPassword/Mac/MPPasswordWindowController.xib +++ b/MasterPassword/Mac/MPPasswordWindowController.xib @@ -40,7 +40,7 @@ 287 2 - {{600, 530}, {480, 159}} + {{600, 530}, {480, 200}} 611845120 Master Password NSPanel @@ -56,24 +56,25 @@ 268 - + 268 - {{17, 20}, {446, 17}} + {{131, 163}, {219, 17}} - _NS:1505 + + _NS:1535 YES - + 68157504 - 138413056 - Hit enter to copy the password. + 272630784 + Maarten Billemont's password for: LucidaGrande 13 1044 - _NS:1505 - + _NS:1535 + 6 System @@ -83,6 +84,34 @@ MC42NjY2NjY2NjY3AA + + 6 + System + controlTextColor + + 3 + MAA + + + + NO + + + + 268 + {{17, 20}, {446, 17}} + + + _NS:1505 + YES + + 68157504 + 138413056 + Hit enter to copy the password. + + _NS:1505 + + 6 System @@ -98,7 +127,7 @@ 268 - {{140, 117}, {200, 22}} + {{140, 133}, {200, 22}} _NS:9 @@ -106,7 +135,7 @@ -1804599231 138413120 - + apple.com Site name _NS:9 @@ -122,10 +151,7 @@ 6 System textColor - - 3 - MAA - + NO @@ -133,9 +159,18 @@ 268 - {{17, 45}, {446, 64}} + {{17, 61}, {446, 64}} + + 1 + 1 + 1 + + 3 + MCAwLjYAA + + _NS:9 {250, 750} YES @@ -160,24 +195,26 @@ NO - {480, 159} + {480, 200} + _NS:9 NSView 268 - {{224, 63}, {32, 32}} + {{224, 84}, {32, 32}} + _NS:945 28682 100 - {480, 159} + {480, 200} - + YES _NS:21 @@ -213,6 +250,38 @@ 215 + + + userLabel + + + + 223 + + + + siteField + + + + 224 + + + + contentField + + + + 225 + + + + tipField + + + + 226 + delegate @@ -379,38 +448,6 @@ 143 - - - 3 - 0 - - 3 - 1 - - 20 - - 1000 - - 8 - 29 - 3 - - - - 9 - 0 - - 9 - 1 - - 0.0 - - 1000 - - 6 - 24 - 2 - 4 @@ -459,7 +496,71 @@ 29 3 + + + 9 + 0 + + 9 + 1 + + 0.0 + + 1000 + + 6 + 24 + 2 + + + + 3 + 0 + + 3 + 1 + + 20 + + 1000 + + 8 + 29 + 3 + + + 3 + 0 + + 4 + 1 + + 8 + + 1000 + + 6 + 24 + 3 + + + + 9 + 0 + + 9 + 1 + + 0.0 + + 1000 + + 6 + 24 + 2 + + 3 0 @@ -508,8 +609,9 @@ 3 - + + @@ -629,20 +731,43 @@ - 210 - + 216 + + + + - 212 + 217 + + + + + 218 - 213 + 219 + + + + + 221 + + 222 + + + + + 210 + + + @@ -652,12 +777,14 @@ + + + + - - com.apple.InterfaceBuilder.CocoaPlugin @@ -667,11 +794,19 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + + + com.apple.InterfaceBuilder.CocoaPlugin + + + + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -686,12 +821,17 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin @@ -708,7 +848,7 @@ - 215 + 230 0 diff --git a/MasterPassword/Mac/en.lproj/MainMenu.xib b/MasterPassword/Mac/en.lproj/MainMenu.xib index 28c3d045..b94c332f 100644 --- a/MasterPassword/Mac/en.lproj/MainMenu.xib +++ b/MasterPassword/Mac/en.lproj/MainMenu.xib @@ -47,27 +47,19 @@ - - - Show - - 2147483647 - - NSImage - NSMenuCheckmark - - - NSImage - NSMenuMixedState - - Users 2147483647 - - + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + submenuAction: Users @@ -78,8 +70,8 @@ New User 2147483647 - - + + @@ -88,8 +80,8 @@ 2147483647 - - + + @@ -99,8 +91,8 @@ Preferences 2147483647 - - + + submenuAction: Preferences @@ -110,8 +102,8 @@ Use iCloud 2147483647 - - + + @@ -119,8 +111,8 @@ Synchronize available sites from your iCloud account. 2147483647 - - + + Synchronize available sites from your iCloud account. @@ -141,8 +133,8 @@ Remember Password 2147483647 - - + + @@ -150,8 +142,8 @@ Remember the password while the application is running. 2147483647 - - + + Remember the password while the application is running. @@ -162,8 +154,8 @@ Save Password 2147483647 - - + + @@ -171,8 +163,8 @@ Save the password in your keychain so you don't need to enter it again. 2147483647 - - + + Save the password in your keychain so you don't need to enter it again. @@ -189,25 +181,35 @@ 2147483647 - - + + + + + + Show + p + 1310720 + 2147483647 + + YES Lock - + p + 1835008 2147483647 - - + + Quit 2147483647 - - + + YES @@ -392,9 +394,9 @@ - + diff --git a/MasterPassword/iOS/MPMainViewController.m b/MasterPassword/iOS/MPMainViewController.m index a2e3d967..603c5007 100644 --- a/MasterPassword/iOS/MPMainViewController.m +++ b/MasterPassword/iOS/MPMainViewController.m @@ -178,30 +178,11 @@ }]; if ([MPAppDelegate get].activeUser) - [[MPAppDelegate get].managedObjectContextIfReady performBlock:^void() { - NSError *error = nil; - NSFetchRequest *migrationRequest = [NSFetchRequest - fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; - migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d", MPAlgorithmDefaultVersion]; - NSArray *migrationElements = [[MPAppDelegate get].managedObjectContextIfReady executeFetchRequest:migrationRequest - error:&error]; - if (!migrationElements) { - err(@"While looking for elements to migrate: %@", error); - return; - } - - BOOL didRequireExplicitMigration = [MPAppDelegate_Shared get].activeUser.requiresExplicitMigration; - if (didRequireExplicitMigration) - [MPAppDelegate_Shared get].activeUser.requiresExplicitMigration = NO; - for (MPElementEntity *migrationElement in migrationElements) - if (![migrationElement migrateExplicitly:NO]) - [MPAppDelegate_Shared get].activeUser.requiresExplicitMigration = YES; - - if (!didRequireExplicitMigration && [MPAppDelegate_Shared get].activeUser.requiresExplicitMigration) + [MPAlgorithmDefault migrateUser:[MPAppDelegate get].activeUser completion:^(BOOL userRequiresNewMigration) { + if (userRequiresNewMigration) [UIView animateWithDuration:0.3f animations:^{ self.outdatedAlertContainer.alpha = 1; }]; - }]; [[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Main"]; @@ -794,6 +775,7 @@ - (void)didSelectElement:(MPElementEntity *)element { inf(@"Selected: %@", element.name); + dbg(@"Element:\n%@", [element debugDescription]); [self closeAlert];