diff --git a/External/Pearl b/External/Pearl index e55ef687..009482a0 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit e55ef6876ee26f61a7cd2c075fc1e7a942016de0 +Subproject commit 009482a08a2a05e9856c2158c1040d01aeedb5ff diff --git a/MasterPassword/MPAppDelegate_Key.m b/MasterPassword/MPAppDelegate_Key.m index 62a4473d..5c5dccff 100644 --- a/MasterPassword/MPAppDelegate_Key.m +++ b/MasterPassword/MPAppDelegate_Key.m @@ -15,17 +15,17 @@ static NSDictionary *keyQuery(MPUserEntity *user) { return [PearlKeyChain createQueryForClass:kSecClassGenericPassword attributes:[NSDictionary dictionaryWithObjectsAndKeys: - @"Saved Master Password", (__bridge id)kSecAttrService, - user.name, (__bridge id)kSecAttrAccount, - nil] - matches:nil]; + @"Saved Master Password", (__bridge id)kSecAttrService, + user.name, (__bridge id)kSecAttrAccount, + nil] + matches:nil]; } - (NSData *)loadSavedKeyFor:(MPUserEntity *)user { NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)]; if (key) - inf(@"Found key (for: %@) in keychain.", user.name); + inf(@"Found key (for: %@) in keychain.", user.name); else { user.saveKey = NO; @@ -44,11 +44,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) { inf(@"Updating key in keychain."); [PearlKeyChain addOrUpdateItemForQuery:keyQuery(user) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: - self.key, (__bridge id) kSecValueData, -#if TARGET_OS_IPHONE - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id) kSecAttrAccessible, -#endif - nil]]; + self.key, (__bridge id)kSecValueData, + #if TARGET_OS_IPHONE + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible, + #endif + nil]]; } } } @@ -72,7 +72,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { - (void)signOut { - self.key = nil; + self.key = nil; self.activeUser = nil; [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self]; @@ -93,22 +93,23 @@ static NSDictionary *keyQuery(MPUserEntity *user) { // Method 2: Depending on the user's saveKey, load or remove the key from the keychain. if (!user.saveKey) - // Key should not be stored in keychain. Delete it. + // Key should not be stored in keychain. Delete it. [self forgetSavedKeyFor:user]; - else if (!tryKey) { - // Key should be saved in keychain. Load it. - if ((tryKey = [self loadSavedKeyFor:user])) - if (![user.keyID isEqual:keyIDForKey(tryKey)]) { - // Loaded password doesn't match user's keyID. Forget saved password: it is incorrect. - tryKey = nil; - [self forgetSavedKeyFor:user]; + else + if (!tryKey) { + // Key should be saved in keychain. Load it. + if ((tryKey = [self loadSavedKeyFor:user])) + if (![user.keyID isEqual:keyIDForKey(tryKey)]) { + // Loaded password doesn't match user's keyID. Forget saved password: it is incorrect. + tryKey = nil; + [self forgetSavedKeyFor:user]; #ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch]; + [TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch]; #endif - } - } + } + } // Method 3: Check the given master password string. if (!tryKey) { @@ -117,9 +118,9 @@ static NSDictionary *keyQuery(MPUserEntity *user) { if (![user.keyID isEqual:keyIDForKey(tryKey)]) { tryKey = nil; - #ifdef TESTFLIGHT_SDK_VERSION +#ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch]; - #endif +#endif } } @@ -132,7 +133,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { [self storeSavedKeyFor:user]; } - user.lastUsed = [NSDate date]; + user.lastUsed = [NSDate date]; self.activeUser = user; [[MPAppDelegate_Shared get] saveContext]; diff --git a/MasterPassword/MPAppDelegate_Shared.h b/MasterPassword/MPAppDelegate_Shared.h index 7353053f..4bc6fde8 100644 --- a/MasterPassword/MPAppDelegate_Shared.h +++ b/MasterPassword/MPAppDelegate_Shared.h @@ -9,13 +9,14 @@ #import "MPEntities.h" #if TARGET_OS_IPHONE + @interface MPAppDelegate_Shared : PearlAppDelegate #else @interface MPAppDelegate_Shared : NSObject #endif -@property (strong, nonatomic) MPUserEntity *activeUser; -@property (strong, nonatomic) NSData *key; +@property (strong, nonatomic) MPUserEntity *activeUser; +@property (strong, nonatomic) NSData *key; + (MPAppDelegate_Shared *)get; diff --git a/MasterPassword/MPAppDelegate_Shared.m b/MasterPassword/MPAppDelegate_Shared.m index 014cbbaa..e1e27e77 100644 --- a/MasterPassword/MPAppDelegate_Shared.m +++ b/MasterPassword/MPAppDelegate_Shared.m @@ -14,7 +14,7 @@ @synthesize activeUser; + (MPAppDelegate_Shared *)get { - + #if TARGET_OS_IPHONE return (MPAppDelegate_Shared *)[UIApplication sharedApplication].delegate; #elif defined (__MAC_OS_X_VERSION_MIN_REQUIRED) diff --git a/MasterPassword/MPAppDelegate_Store.h b/MasterPassword/MPAppDelegate_Store.h index 31ff0177..00aa509b 100644 --- a/MasterPassword/MPAppDelegate_Store.h +++ b/MasterPassword/MPAppDelegate_Store.h @@ -18,7 +18,7 @@ typedef enum { MPImportResultInternalError, } MPImportResult; -@interface MPAppDelegate_Shared (Store) +@interface MPAppDelegate_Shared (Store) + (NSManagedObjectContext *)managedObjectContext; + (NSManagedObjectModel *)managedObjectModel; diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m index 88332bf6..9812758e 100644 --- a/MasterPassword/MPAppDelegate_Store.m +++ b/MasterPassword/MPAppDelegate_Store.m @@ -15,79 +15,80 @@ static NSDateFormatter *rfc3339DateFormatter = nil; #pragma mark - Core Data setup + (NSManagedObjectContext *)managedObjectContext { - + return [[self get] managedObjectContext]; } + (NSManagedObjectModel *)managedObjectModel { - + return [[self get] managedObjectModel]; } - (NSManagedObjectModel *)managedObjectModel { - + static NSManagedObjectModel *managedObjectModel = nil; if (managedObjectModel) return managedObjectModel; - + NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"]; return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; } - (NSManagedObjectContext *)managedObjectContext { - + static NSManagedObjectContext *managedObjectContext = nil; if (managedObjectContext) return managedObjectContext; - - return [PearlLazy lazyObjectLoadedFrom:^id{ + + return [PearlLazy lazyObjectLoadedFrom:^id { NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; assert(coordinator); - + managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [managedObjectContext performBlockAndWait:^{ managedObjectContext.persistentStoreCoordinator = coordinator; - managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; + managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; }]; - + return managedObjectContext; }]; } - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { - + // Start loading the store. [self storeManager]; - - return [PearlLazy lazyObjectLoadedFrom:^id{ + + return [PearlLazy lazyObjectLoadedFrom:^id { // Wait until the storeManager is ready. - for(__block BOOL isReady = [self storeManager].isReady; !isReady;) { + for (__block BOOL isReady = [self storeManager].isReady; !isReady;) { [NSThread sleepForTimeInterval:0.1]; dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ isReady = [self storeManager].isReady; }); } - + assert([self storeManager].isReady); return [self storeManager].persistentStoreCoordinator; }]; } - (UbiquityStoreManager *)storeManager { - + static UbiquityStoreManager *storeManager = nil; if (storeManager) return storeManager; - + storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel] - localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"] - containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared" + localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"] + containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared" #if TARGET_OS_IPHONE - additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSPersistentStoreFileProtectionKey] + additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete + forKey:NSPersistentStoreFileProtectionKey] #else additionalStoreOptions:nil #endif - ]; + ]; storeManager.delegate = self; #ifdef DEBUG storeManager.hardResetEnabled = YES; @@ -95,9 +96,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil; #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication] queue:nil - usingBlock:^(NSNotification *note) { - [storeManager checkiCloudStatus]; - }]; + usingBlock:^(NSNotification *note) { + [storeManager checkiCloudStatus]; + }]; #else [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification object:[NSApplication sharedApplication] queue:nil @@ -108,9 +109,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil; #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication] queue:nil - usingBlock:^(NSNotification *note) { - [self saveContext]; - }]; + usingBlock:^(NSNotification *note) { + [self saveContext]; + }]; #else [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication] queue:nil @@ -118,52 +119,53 @@ static NSDateFormatter *rfc3339DateFormatter = nil; [self saveContext]; }]; #endif - + return storeManager; } - (void)saveContext { - + [self.managedObjectContext performBlock:^{ NSError *error = nil; if ([self.managedObjectContext hasChanges]) if (![self.managedObjectContext save:&error]) - err(@"While saving context: %@", error); + err(@"While saving context: %@", error); }]; } #pragma mark - UbiquityStoreManagerDelegate - (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm { - + return self.managedObjectContext; } - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message { - + dbg(@"[StoreManager] %@", message); } - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - + // manager.iCloudEnabled is more reliable (eg. iOS tampers with didSwitch a bit) iCloudEnabled = manager.iCloudEnabled; - + #ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:iCloudEnabled? MPTestFlightCheckpointCloudEnabled: MPTestFlightCheckpointCloudDisabled]; #endif - + inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO"); [MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled]; } -- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause context:(id)context { - +- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause + context:(id)context { + #ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:PearlString(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)]; #endif err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error); - + switch (cause) { case UbiquityStoreManagerErrorCauseDeleteStore: case UbiquityStoreManagerErrorCauseDeleteLogs: @@ -177,7 +179,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil; wrn(@"Local store could not be opened, resetting it."); manager.hardResetEnabled = YES; [manager hardResetLocalStorage]; - + [NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."]; return; } @@ -196,10 +198,10 @@ static NSDateFormatter *rfc3339DateFormatter = nil; #pragma mark - Import / Export - (void)loadRFC3339DateFormatter { - + if (rfc3339DateFormatter) return; - + rfc3339DateFormatter = [NSDateFormatter new]; NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; [rfc3339DateFormatter setLocale:enUSPOSIXLocale]; @@ -209,36 +211,36 @@ static NSDateFormatter *rfc3339DateFormatter = nil; - (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation { - + [self loadRFC3339DateFormatter]; - + static NSRegularExpression *headerPattern, *sitePattern; __autoreleasing NSError *error; if (!headerPattern) { headerPattern = [[NSRegularExpression alloc] - initWithPattern:@"^#[[:space:]]*([^:]+): (.*)" - options:0 error:&error]; + initWithPattern:@"^#[[:space:]]*([^:]+): (.*)" + options:0 error:&error]; if (error) - err(@"Error loading the header pattern: %@", error); + err(@"Error loading the header pattern: %@", error); } if (!sitePattern) { sitePattern = [[NSRegularExpression alloc] - initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)" - options:0 error:&error]; + initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)" + options:0 error:&error]; if (error) - err(@"Error loading the site pattern: %@", error); + err(@"Error loading the site pattern: %@", error); } if (!headerPattern || !sitePattern) return MPImportResultInternalError; - + NSString *keyIDHex = nil, *userName = nil; MPUserEntity *user = nil; BOOL headerStarted = NO, headerEnded = NO; - NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - NSMutableSet *elementsToDelete = [NSMutableSet set]; + NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + NSMutableSet *elementsToDelete = [NSMutableSet set]; NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]]; - NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; - for(NSString *importedSiteLine in importedSiteLines) { + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; + for (NSString *importedSiteLine in importedSiteLines) { if ([importedSiteLine hasPrefix:@"#"]) { // Comment or header if (!headerStarted) { @@ -252,18 +254,19 @@ static NSDateFormatter *rfc3339DateFormatter = nil; headerEnded = YES; continue; } - + // Header if ([headerPattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) { err(@"Invalid header format in line: %@", importedSiteLine); return MPImportResultMalformedInput; } - NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject]; - NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]]; - NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]]; + NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 + range:NSMakeRange(0, [importedSiteLine length])] lastObject]; + NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]]; + NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]]; if ([key isEqualToString:@"User Name"]) { userName = value; - + NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName]; user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject]; @@ -272,7 +275,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil; if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]]) return MPImportResultInvalidPassword; } - + continue; } if (!headerEnded) @@ -281,81 +284,83 @@ static NSDateFormatter *rfc3339DateFormatter = nil; return MPImportResultMalformedInput; if (![importedSiteLine length]) continue; - + // Site if ([sitePattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) { err(@"Invalid site format in line: %@", importedSiteLine); return MPImportResultMalformedInput; } - NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject]; - NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; - NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; - NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; - NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; - NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; - + NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0 + range:NSMakeRange(0, [importedSiteLine length])] lastObject]; + NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; + NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; + NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; + NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; + NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; + // Find existing site. if (user) { fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user]; NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error]; if (error) - err(@"Couldn't search existing sites: %@", error); + err(@"Couldn't search existing sites: %@", error); if (!existingSites) return MPImportResultInternalError; - + [elementsToDelete addObjectsFromArray:existingSites]; [importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]]; } } - + // Ask for confirmation to import these sites. if (!confirmation([importedSiteElements count], [elementsToDelete count])) return MPImportResultCancelled; - + // Delete existing sites. [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]); [self.managedObjectContext deleteObject:obj]; }]; [self saveContext]; - + // Import new sites. if (!user) { - user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) inManagedObjectContext:self.managedObjectContext]; - user.name = userName; + user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) + inManagedObjectContext:self.managedObjectContext]; + user.name = userName; user.keyID = [keyIDHex decodeHex]; } for (NSArray *siteElements in importedSiteElements) { - NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]]; - NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue]; - MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue]; + NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]]; + NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue]; + MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue]; NSString *name = [siteElements objectAtIndex:3]; NSString *exportContent = [siteElements objectAtIndex:4]; - + // Create new site. MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) inManagedObjectContext:self.managedObjectContext]; - element.name = name; - element.user = user; - element.type = type; - element.uses = uses; + element.name = name; + element.user = user; + element.type = type; + element.uses = uses; element.lastUsed = lastUsed; if ([exportContent length]) [element importContent:exportContent]; } [self saveContext]; - + #ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported]; #endif - + return MPImportResultSuccess; } - (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords { - + [self loadRFC3339DateFormatter]; - + // Header. NSMutableString *export = [NSMutableString new]; [export appendFormat:@"# Master Password site export\n"]; @@ -377,31 +382,33 @@ static NSDateFormatter *rfc3339DateFormatter = nil; [export appendFormat:@"#\n"]; [export appendFormat:@"# Last Times Password Site\tSite\n"]; [export appendFormat:@"# used used type name\tpassword\n"]; - + // Sites. for (MPElementEntity *element in self.activeUser.elements) { - NSDate *lastUsed = element.lastUsed; - NSUInteger uses = element.uses; - MPElementType type = element.type; - NSString *name = element.name; - NSString *content = nil; - + NSDate *lastUsed = element.lastUsed; + NSUInteger uses = element.uses; + MPElementType type = element.type; + NSString *name = element.name; + NSString *content = nil; + // Determine the content to export. if (!(type & MPElementFeatureDevicePrivate)) { if (showPasswords) content = element.content; - else if (type & MPElementFeatureExportContent) - content = element.exportContent; + else + if (type & MPElementFeatureExportContent) + content = element.exportContent; } - + [export appendFormat:@"%@ %8d %8d %20s\t%@\n", - [rfc3339DateFormatter stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content? content: @""]; + [rfc3339DateFormatter stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content + ? content: @""]; } - + #ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported]; #endif - + return export; } diff --git a/MasterPassword/MPConfig.m b/MasterPassword/MPConfig.m index 1a3e9d7b..845cb17a 100644 --- a/MasterPassword/MPConfig.m +++ b/MasterPassword/MPConfig.m @@ -6,32 +6,31 @@ // Copyright (c) 2012 Lyndir. All rights reserved. // -#import "MPConfig.h" #import "MPAppDelegate.h" @implementation MPConfig @dynamic rememberLogin, iCloud, iCloudDecided; - (id)init { - - if(!(self = [super init])) + + if (!(self = [super init])) return nil; - + [self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)), - - [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)), - [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)), - [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)), - nil]]; - + [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)), + + [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)), + [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)), + [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)), + nil]]; + self.delegate = [MPAppDelegate get]; - + return self; } + (MPConfig *)get { - + return (MPConfig *)[super get]; } diff --git a/MasterPassword/MPElementEntity.h b/MasterPassword/MPElementEntity.h index 15a0cb22..9f69efd1 100644 --- a/MasterPassword/MPElementEntity.h +++ b/MasterPassword/MPElementEntity.h @@ -14,10 +14,10 @@ @interface MPElementEntity : NSManagedObject @property (nonatomic, retain) id content; -@property (nonatomic, retain) NSDate * lastUsed; -@property (nonatomic, retain) NSString * name; -@property (nonatomic, retain) NSNumber * type_; -@property (nonatomic, retain) NSNumber * uses_; +@property (nonatomic, retain) NSDate *lastUsed; +@property (nonatomic, retain) NSString *name; +@property (nonatomic, retain) NSNumber *type_; +@property (nonatomic, retain) NSNumber *uses_; @property (nonatomic, retain) MPUserEntity *user; @end diff --git a/MasterPassword/MPElementEntity.m b/MasterPassword/MPElementEntity.m index 9118fdfb..ca332060 100644 --- a/MasterPassword/MPElementEntity.m +++ b/MasterPassword/MPElementEntity.m @@ -7,7 +7,6 @@ // #import "MPElementEntity.h" -#import "MPUserEntity.h" @implementation MPElementEntity diff --git a/MasterPassword/MPElementGeneratedEntity.h b/MasterPassword/MPElementGeneratedEntity.h index efc1f88c..68a793a7 100644 --- a/MasterPassword/MPElementGeneratedEntity.h +++ b/MasterPassword/MPElementGeneratedEntity.h @@ -13,6 +13,6 @@ @interface MPElementGeneratedEntity : MPElementEntity -@property (nonatomic, retain) NSNumber * counter_; +@property (nonatomic, retain) NSNumber *counter_; @end diff --git a/MasterPassword/MPEntities.h b/MasterPassword/MPEntities.h index 224957a1..71ed04a1 100644 --- a/MasterPassword/MPEntities.h +++ b/MasterPassword/MPEntities.h @@ -17,7 +17,7 @@ @interface MPElementEntity (MP) @property (assign) MPElementType type; -@property (assign) NSUInteger uses; +@property (assign) NSUInteger uses; - (NSUInteger)use; - (NSString *)exportContent; @@ -34,6 +34,6 @@ @interface MPUserEntity (MP) @property (assign) NSUInteger avatar; -@property (assign) BOOL saveKey; +@property (assign) BOOL saveKey; @end diff --git a/MasterPassword/MPEntities.m b/MasterPassword/MPEntities.m index 4900caa8..545be914 100644 --- a/MasterPassword/MPEntities.m +++ b/MasterPassword/MPEntities.m @@ -13,12 +13,12 @@ @implementation MPElementEntity (MP) - (MPElementType)type { - + return (MPElementType)[self.type_ unsignedIntegerValue]; } - (void)setType:(MPElementType)type { - + self.type_ = PearlUnsignedInteger(type); } @@ -34,32 +34,32 @@ - (NSUInteger)use { - + self.lastUsed = [NSDate date]; return ++self.uses; } - (id)content { - + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil]; } - (NSString *)exportContent { - + return nil; } - (void)importContent:(NSString *)content { - + } - (NSString *)description { - + return PearlString(@"%@:%@", [self class], [self name]); } - (NSString *)debugDescription { - + return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}", NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed); } @@ -84,10 +84,10 @@ err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type); return nil; } - + if (![self.name length]) return nil; - + return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter); } @@ -96,55 +96,55 @@ @implementation MPElementStoredEntity (MP) + (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name { - + return [PearlKeyChain createQueryForClass:kSecClassGenericPassword - attributes:[NSDictionary dictionaryWithObjectsAndKeys: - @"DevicePrivate", (__bridge id)kSecAttrService, - name, (__bridge id)kSecAttrAccount, - nil] - matches:nil]; + attributes:[NSDictionary dictionaryWithObjectsAndKeys: + @"DevicePrivate", (__bridge id)kSecAttrService, + name, (__bridge id)kSecAttrAccount, + nil] + matches:nil]; } - (id)content { - + assert(self.type & MPElementTypeClassStored); - + NSData *encryptedContent; if (self.type & MPElementFeatureDevicePrivate) encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]]; else encryptedContent = self.contentObject; - + NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize] - padding:YES]; + padding:YES]; return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding]; } - (void)setContent:(id)content { - + NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize] - padding:YES]; - + padding:YES]; + if (self.type & MPElementFeatureDevicePrivate) { [PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name] - withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: - encryptedContent, (__bridge id)kSecValueData, -#if TARGET_OS_IPHONE - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible, -#endif - nil]]; + withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: + encryptedContent, (__bridge id)kSecValueData, + #if TARGET_OS_IPHONE + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible, + #endif + nil]]; self.contentObject = nil; } else self.contentObject = encryptedContent; } - (NSString *)exportContent { - + return [self.contentObject encodeBase64]; } - (void)importContent:(NSString *)content { - + self.contentObject = [content decodeBase64]; } diff --git a/MasterPassword/MPTypes.h b/MasterPassword/MPTypes.h index aaa1fb68..8b111a78 100644 --- a/MasterPassword/MPTypes.h +++ b/MasterPassword/MPTypes.h @@ -18,28 +18,28 @@ typedef enum { typedef enum { /** Generate the password. */ - MPElementTypeClassGenerated = 1 << 4, + MPElementTypeClassGenerated = 1 << 4, /** Store the password. */ - MPElementTypeClassStored = 1 << 5, + MPElementTypeClassStored = 1 << 5, } MPElementTypeClass; typedef enum { /** Export the key-protected content data. */ - MPElementFeatureExportContent = 1 << 10, + MPElementFeatureExportContent = 1 << 10, /** Never export content. */ - MPElementFeatureDevicePrivate = 1 << 11, + MPElementFeatureDevicePrivate = 1 << 11, } MPElementFeature; typedef enum { - MPElementTypeGeneratedSecure = 0x0 | MPElementTypeClassGenerated | 0x0, - MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0, - MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0, - MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0, - MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0, - MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0, - - MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent, - MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate, + MPElementTypeGeneratedSecure = 0x0 | MPElementTypeClassGenerated | 0x0, + MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0, + MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0, + MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0, + MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0, + MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0, + + MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent, + MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate, } MPElementType; #define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction" @@ -77,9 +77,9 @@ typedef enum { #define MPNotificationKeyForgotten @"MPNotificationKeyForgotten" #define MPNotificationElementUsed @"MPNotificationElementUsed" -NSData *keyForPassword(NSString *password, NSString *username); -NSData *keyIDForPassword(NSString *password, NSString *username); -NSData *keyIDForKey(NSData *key); +NSData *keyForPassword(NSString *password, NSString *username); +NSData *keyIDForPassword(NSString *password, NSString *username); +NSData *keyIDForKey(NSData *key); NSString *NSStringFromMPElementType(MPElementType type); NSString *ClassNameFromMPElementType(MPElementType type); Class ClassFromMPElementType(MPElementType type); diff --git a/MasterPassword/MPTypes.m b/MasterPassword/MPTypes.m index f692022c..fee6e6df 100644 --- a/MasterPassword/MPTypes.m +++ b/MasterPassword/MPTypes.m @@ -7,7 +7,6 @@ // #import "MPTypes.h" -#import "MPElementGeneratedEntity.h" #import "MPElementStoredEntity.h" @@ -21,101 +20,106 @@ NSData *keyForPassword(NSString *password, NSString *username) { uint32_t nusernameLength = htonl(username.length); NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding] - usingSalt:[NSData dataByConcatenatingDatas: - [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], - [NSData dataWithBytes:&nusernameLength length:sizeof(nusernameLength)], - [username dataUsingEncoding:NSUTF8StringEncoding], - nil] N:MP_N r:MP_r p:MP_p]; - + usingSalt:[NSData dataByConcatenatingDatas: + [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], + [NSData dataWithBytes:&nusernameLength + length:sizeof(nusernameLength)], + [username dataUsingEncoding:NSUTF8StringEncoding], + nil] N:MP_N r:MP_r p:MP_p]; + trc(@"User: %@, password: %@ derives to key ID: %@", username, password, [keyIDForKey(key) encodeHex]); return key; } + NSData *keyIDForPassword(NSString *password, NSString *username) { - + return keyIDForKey(keyForPassword(password, username)); } + NSData *keyIDForKey(NSData *key) { - + return [key hashWith:MP_hash]; } + NSString *NSStringFromMPElementType(MPElementType type) { - + if (!type) return nil; - + switch (type) { case MPElementTypeGeneratedSecure: return @"Secure Password"; - + case MPElementTypeGeneratedLong: return @"Long Password"; case MPElementTypeGeneratedMedium: return @"Medium Password"; - + case MPElementTypeGeneratedShort: return @"Short Password"; - + case MPElementTypeGeneratedBasic: return @"Basic Password"; - + case MPElementTypeGeneratedPIN: return @"PIN"; - + case MPElementTypeStoredPersonal: return @"Personal Password"; - + case MPElementTypeStoredDevicePrivate: return @"Device Private Password"; - + default: Throw(@"Type not supported: %d", type); } } Class ClassFromMPElementType(MPElementType type) { - + if (!type) return nil; - + switch (type) { case MPElementTypeGeneratedSecure: return [MPElementGeneratedEntity class]; - + case MPElementTypeGeneratedLong: return [MPElementGeneratedEntity class]; case MPElementTypeGeneratedMedium: return [MPElementGeneratedEntity class]; - + case MPElementTypeGeneratedShort: return [MPElementGeneratedEntity class]; - + case MPElementTypeGeneratedBasic: return [MPElementGeneratedEntity class]; - + case MPElementTypeGeneratedPIN: return [MPElementGeneratedEntity class]; - + case MPElementTypeStoredPersonal: return [MPElementStoredEntity class]; - + case MPElementTypeStoredDevicePrivate: return [MPElementStoredEntity class]; - + default: Throw(@"Type not supported: %d", type); } } NSString *ClassNameFromMPElementType(MPElementType type) { - + return NSStringFromClass(ClassFromMPElementType(type)); } static NSDictionary *MPTypes_ciphers = nil; + NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) { - + if (!(type & MPElementTypeClassGenerated)) { err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name); return nil; @@ -129,46 +133,47 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui return nil; } if (!counter) - // Counter unset, go into OTP mode. - // Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in. + // Counter unset, go into OTP mode. + // Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in. counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300; - + if (MPTypes_ciphers == nil) MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers" withExtension:@"plist"]]; - + // Determine the seed whose bytes will be used for calculating a password trc(@"seed from: hmac-sha256(key, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter); uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length); NSData *seed = [[NSData dataByConcatenatingDatas: - [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], - [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)], - [name dataUsingEncoding:NSUTF8StringEncoding], - [NSData dataWithBytes:&ncounter length:sizeof(ncounter)], - nil] - hmacWith:PearlHashSHA256 key:key]; + [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], + [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)], + [name dataUsingEncoding:NSUTF8StringEncoding], + [NSData dataWithBytes:&ncounter length:sizeof(ncounter)], + nil] + hmacWith:PearlHashSHA256 key:key]; trc(@"seed is: %@", seed); const char *seedBytes = seed.bytes; - + // Determine the cipher from the first seed byte. assert([seed length]); - NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)] - valueForKey:NSStringFromMPElementType(type)]; - NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]]; + NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)] + valueForKey:NSStringFromMPElementType(type)]; + NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]]; trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher); - + // Encode the content, character by character, using subsequent seed bytes and the cipher. assert([seed length] >= [cipher length] + 1); NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]]; for (NSUInteger c = 0; c < [cipher length]; ++c) { uint16_t keyByte = htons(seedBytes[c + 1]); - NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)]; + NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)]; NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass]; - NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)]; - + NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], + 1)]; + trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character); [content appendString:character]; } - + return content; } diff --git a/MasterPassword/MPUserEntity.h b/MasterPassword/MPUserEntity.h index fa24fb1c..45859195 100644 --- a/MasterPassword/MPUserEntity.h +++ b/MasterPassword/MPUserEntity.h @@ -13,12 +13,12 @@ @interface MPUserEntity : NSManagedObject -@property (nonatomic, retain) NSNumber * avatar_; -@property (nonatomic, retain) NSData * keyID; -@property (nonatomic, retain) NSDate * lastUsed; -@property (nonatomic, retain) NSString * name; -@property (nonatomic, retain) NSNumber * saveKey_; -@property (nonatomic, retain) NSSet *elements; +@property (nonatomic, retain) NSNumber *avatar_; +@property (nonatomic, retain) NSData *keyID; +@property (nonatomic, retain) NSDate *lastUsed; +@property (nonatomic, retain) NSString *name; +@property (nonatomic, retain) NSNumber *saveKey_; +@property (nonatomic, retain) NSSet *elements; @end @interface MPUserEntity (CoreDataGeneratedAccessors) diff --git a/MasterPassword/MPUserEntity.m b/MasterPassword/MPUserEntity.m index f4d74a98..23032da5 100644 --- a/MasterPassword/MPUserEntity.m +++ b/MasterPassword/MPUserEntity.m @@ -7,7 +7,6 @@ // #import "MPUserEntity.h" -#import "MPElementEntity.h" @implementation MPUserEntity diff --git a/MasterPassword/Mac/MPAppDelegate.h b/MasterPassword/Mac/MPAppDelegate.h index 593c09c6..324dcf1c 100644 --- a/MasterPassword/Mac/MPAppDelegate.h +++ b/MasterPassword/Mac/MPAppDelegate.h @@ -10,16 +10,16 @@ #import "MPAppDelegate_Shared.h" #import "MPPasswordWindowController.h" -@interface MPAppDelegate : MPAppDelegate_Shared +@interface MPAppDelegate : MPAppDelegate_Shared -@property (strong) NSStatusItem *statusItem; -@property (strong) MPPasswordWindowController *passwordWindow; -@property (weak) IBOutlet NSMenuItem *lockItem; -@property (weak) IBOutlet NSMenuItem *showItem; -@property (strong) IBOutlet NSMenu *statusMenu; -@property (weak) IBOutlet NSMenuItem *useICloudItem; -@property (weak) IBOutlet NSMenuItem *rememberPasswordItem; -@property (weak) IBOutlet NSMenuItem *savePasswordItem; +@property (strong) NSStatusItem *statusItem; +@property (strong) MPPasswordWindowController *passwordWindow; +@property (weak) IBOutlet NSMenuItem *lockItem; +@property (weak) IBOutlet NSMenuItem *showItem; +@property (strong) IBOutlet NSMenu *statusMenu; +@property (weak) IBOutlet NSMenuItem *useICloudItem; +@property (weak) IBOutlet NSMenuItem *rememberPasswordItem; +@property (weak) IBOutlet NSMenuItem *savePasswordItem; + (MPAppDelegate *)get; diff --git a/MasterPassword/Mac/MPAppDelegate.m b/MasterPassword/Mac/MPAppDelegate.m index 37156f2d..1e9f34fc 100644 --- a/MasterPassword/Mac/MPAppDelegate.m +++ b/MasterPassword/Mac/MPAppDelegate.m @@ -28,49 +28,49 @@ @synthesize keyID; #pragma GCC diagnostic ignored "-Wfour-char-constants" -static EventHotKeyID MPShowHotKey = { .signature = 'show', .id = 1 }; +static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1}; + (void)initialize { - + [MPConfig get]; - + #ifdef DEBUG [PearlLogger get].autoprintLevel = PearlLogLevelTrace; #endif } + (MPAppDelegate *)get { - + return (MPAppDelegate *)[super get]; } -static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData){ - +static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { + // Extract the hotkey ID. - EventHotKeyID hotKeyID; - GetEventParameter(theEvent,kEventParamDirectObject,typeEventHotKeyID, - NULL,sizeof(hotKeyID),NULL,&hotKeyID); - + EventHotKeyID hotKeyID; + GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, + NULL, sizeof(hotKeyID), NULL, &hotKeyID); + // Check which hotkey this was. if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) { [((__bridge MPAppDelegate *)userData) activate:nil]; return noErr; } - + return eventNotHandledErr; } - (void)showMenu { - + self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState; - self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState; - self.showItem.enabled = ![self.passwordWindow.window isVisible]; - + self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState; + self.showItem.enabled = ![self.passwordWindow.window isVisible]; + [self.statusItem popUpStatusItemMenu:self.statusMenu]; } - (IBAction)activate:(id)sender { - + if ([[NSApplication sharedApplication] isActive]) [self applicationDidBecomeActive:nil]; else @@ -78,13 +78,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven } - (IBAction)togglePreference:(NSMenuItem *)sender { - + if (sender == useICloudItem) [self.storeManager useiCloudStore:sender.state == NSOffState alertUser:YES]; if (sender == rememberPasswordItem) [MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]]; if (sender == savePasswordItem) - [MPConfig get].saveKey = [NSNumber numberWithBool:![[MPConfig get].saveKey boolValue]]; + [MPConfig get].saveKey = [NSNumber numberWithBool:![[MPConfig get].saveKey boolValue]]; } - (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue { @@ -92,11 +92,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven if (configKey == @selector(rememberKey)) self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState; if (configKey == @selector(saveKey)) - self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState; + self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - + if ([keyPath isEqualToString:@"key"]) { if (self.key) [self.lockItem setEnabled:YES]; @@ -108,49 +108,50 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven } - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window { - + return [[self managedObjectContext] undoManager]; } #pragma mark - NSApplicationDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - + // Setup delegates and listeners. [MPConfig get].delegate = self; [self addObserver:self forKeyPath:@"key" options:0 context:nil]; - + // Initially, use iCloud. if ([[MPConfig get].firstRun boolValue]) [[self storeManager] useiCloudStore:YES alertUser: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.title = @"•••"; + self.statusItem.highlightMode = YES; + self.statusItem.target = self; + self.statusItem.action = @selector(showMenu); + // Global hotkey. EventHotKeyRef hotKeyRef; - EventTypeSpec hotKeyEvents[1] = { { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed } }; - OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents), hotKeyEvents, - (__bridge void *)self, NULL); - if(status != noErr) - 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); + EventTypeSpec hotKeyEvents[1] = {{.eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed}}; + OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents), + hotKeyEvents, + (__bridge void *)self, NULL); + if (status != noErr) + 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); } - (void)applicationWillBecomeActive:(NSNotification *)notification { - + if (!self.passwordWindow) self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"]; } - (void)applicationDidBecomeActive:(NSNotification *)notification { - + static BOOL firstTime = YES; if (firstTime) firstTime = NO; @@ -159,62 +160,61 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven } - (void)applicationWillResignActive:(NSNotification *)notification { - + if (![[MPConfig get].rememberKey boolValue]) self.key = nil; } -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender -{ +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { // Save changes in the application's managed object context before the application terminates. - + if (![self managedObjectContext]) { return NSTerminateNow; } - + if (![[self managedObjectContext] commitEditing]) { NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd)); return NSTerminateCancel; } - + if (![[self managedObjectContext] hasChanges]) { return NSTerminateNow; } - + NSError *error = nil; if (![[self managedObjectContext] save:&error]) { - + // Customize this code block to include application-specific recovery steps. BOOL result = [sender presentError:error]; if (result) { return NSTerminateCancel; } - - NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message"); - NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info"); - NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title"); + + NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message"); + NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info"); + NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title"); NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title"); - NSAlert *alert = [[NSAlert alloc] init]; + NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:question]; [alert setInformativeText:info]; [alert addButtonWithTitle:quitButton]; [alert addButtonWithTitle:cancelButton]; - + NSInteger answer = [alert runModal]; - + if (answer == NSAlertAlternateReturn) { return NSTerminateCancel; } } - + return NSTerminateNow; } #pragma mark - UbiquityStoreManagerDelegate - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - - self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState; + + self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState; self.useICloudItem.enabled = !iCloudEnabled; } diff --git a/MasterPassword/Mac/MPPasswordWindowController.h b/MasterPassword/Mac/MPPasswordWindowController.h index 4cc3ddd9..fa23a013 100644 --- a/MasterPassword/Mac/MPPasswordWindowController.h +++ b/MasterPassword/Mac/MPPasswordWindowController.h @@ -8,8 +8,8 @@ #import -@interface MPPasswordWindowController : NSWindowController { - +@interface MPPasswordWindowController : NSWindowController { + NSString *_content; } diff --git a/MasterPassword/Mac/MPPasswordWindowController.m b/MasterPassword/Mac/MPPasswordWindowController.m index 91bd698c..2e38b582 100644 --- a/MasterPassword/Mac/MPPasswordWindowController.m +++ b/MasterPassword/Mac/MPPasswordWindowController.m @@ -27,10 +27,10 @@ @synthesize tipField; - (void)windowDidLoad { - + [self setContent:@""]; [self.tipField setStringValue:@""]; - + [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil usingBlock:^(NSNotification *note) { [self unlock]; @@ -45,32 +45,32 @@ NSString *newSiteName = [self.siteField stringValue]; BOOL shouldComplete = [self.oldSiteName length] < [newSiteName length]; self.oldSiteName = newSiteName; - + if ([self trySite]) shouldComplete = NO; - + if (shouldComplete) [[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil]; }]; - + [super windowDidLoad]; } - (void)unlock { - + if (![MPAppDelegate get].key) - // Try and load the key from the keychain. + // Try and load the key from the keychain. [[MPAppDelegate get] loadStoredKey]; - + if (![MPAppDelegate get].key) - // Ask the user to set the key through his master password. + // Ask the user to set the key through his master password. dispatch_async(dispatch_get_main_queue(), ^{ if ([MPAppDelegate get].key) return; - - NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked." - defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit" - informativeTextWithFormat:@"Your master password is required to unlock the application."]; + + NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked." + defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit" + informativeTextWithFormat:@"Your master password is required to unlock the application."]; NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)]; [alert setAccessoryView:passwordField]; [alert layout]; @@ -80,50 +80,52 @@ }); } -- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { - +- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { + switch (returnCode) { case NSAlertAlternateReturn: // "Change" button. if ([[NSAlert alertWithMessageText:@"Changing Master Password" defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil - informativeTextWithFormat: - @"This will allow you to log in with a different master password.\n\n" - @"Note that you will only see the sites and passwords for the master password you log in with.\n" - @"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) + informativeTextWithFormat: + @"This will allow you to log in with a different master password.\n\n" + @"Note that you will only see the sites and passwords for the master password you log in with.\n" + @"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) [[MPAppDelegate get] forgetKey]; break; - + case NSAlertOtherReturn: // "Quit" button. [[NSApplication sharedApplication] terminate:self]; return; - + case NSAlertDefaultReturn: // "Unlock" button. [[MPAppDelegate get] tryMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]]; } } -- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index { - +- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words + forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index { + NSString *query = [[control stringValue] substringWithRange:charRange]; if (![query length] || ![MPAppDelegate get].keyID) return nil; - + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]]; - fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@", - query, query, [MPAppDelegate get].activeUser]; - + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@", + query, query, [MPAppDelegate get].activeUser]; + NSError *error = nil; self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error]; if (error) - err(@"Couldn't fetch elements: %@", error); - - NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1]; + err(@"Couldn't fetch elements: %@", error); + + NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1]; if (self.siteResults) for (MPElementEntity *element in self.siteResults) [mutableResults addObject:element.name]; @@ -132,7 +134,7 @@ } - (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector { - + if (commandSelector == @selector(cancel:)) { [self.window close]; return YES; @@ -149,68 +151,68 @@ [self.tipField.animator setAlphaValue:0]; [NSAnimationContext endGrouping]; }); - + [[self findElement] use]; return YES; } else - wrn(@"Couldn't copy password to pasteboard."); + wrn(@"Couldn't copy password to pasteboard."); } - + return NO; } - (void)controlTextDidEndEditing:(NSNotification *)obj { - + if (obj.object == self.siteField) [self trySite]; } - (NSString *)content { - + return _content; } - (void)setContent:(NSString *)content { - + _content = content; - + NSShadow *shadow = [NSShadow new]; - shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f]; - shadow.shadowOffset = NSMakeSize(1.0f, -1.0f); + 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]]]; + [[NSAttributedString alloc] initWithString:_content + attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys: + shadow, NSShadowAttributeName, + paragraph, NSParagraphStyleAttributeName, + nil]]]; } - (BOOL)trySite { - + MPElementEntity *result = [self findElement]; if (!result) { [self setContent:@""]; [self.tipField setStringValue:@""]; return NO; } - + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSString *description = [result.content description]; if (!description) description = @""; - + dispatch_async(dispatch_get_main_queue(), ^{ [self setContent:description]; [self.tipField setStringValue:@"Hit enter to copy the password."]; self.tipField.alphaValue = 1; }); }); - + // For when the app should be able to create new sites. /* else @@ -231,16 +233,16 @@ }); }]; */ - + return YES; } - (MPElementEntity *)findElement { - + for (MPElementEntity *element in self.siteResults) if ([element.name isEqualToString:[self.siteField stringValue]]) return element; - + return nil; } diff --git a/MasterPassword/Mac/MasterPassword-Prefix.pch b/MasterPassword/Mac/MasterPassword-Prefix.pch index 407d6cec..23817792 100644 --- a/MasterPassword/Mac/MasterPassword-Prefix.pch +++ b/MasterPassword/Mac/MasterPassword-Prefix.pch @@ -3,7 +3,9 @@ // #ifdef __OBJC__ - #import + +#import + #endif #import "Pearl-Prefix.pch" diff --git a/MasterPassword/Mac/main.m b/MasterPassword/Mac/main.m index 9dc5011f..60a1522e 100644 --- a/MasterPassword/Mac/main.m +++ b/MasterPassword/Mac/main.m @@ -8,7 +8,7 @@ #import -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { + return NSApplicationMain(argc, (const char **)argv); } diff --git a/MasterPassword/iOS/MPAppDelegate.h b/MasterPassword/iOS/MPAppDelegate.h index d4caccfd..6e508949 100644 --- a/MasterPassword/iOS/MPAppDelegate.h +++ b/MasterPassword/iOS/MPAppDelegate.h @@ -10,7 +10,7 @@ #import #import "MPAppDelegate_Shared.h" -@interface MPAppDelegate : MPAppDelegate_Shared +@interface MPAppDelegate : MPAppDelegate_Shared + (MPAppDelegate *)get; diff --git a/MasterPassword/iOS/MPAppDelegate.m b/MasterPassword/iOS/MPAppDelegate.m index e8a95ccb..343fdf19 100644 --- a/MasterPassword/iOS/MPAppDelegate.m +++ b/MasterPassword/iOS/MPAppDelegate.m @@ -12,8 +12,6 @@ #import "IASKSettingsReader.h" #import "LocalyticsSession.h" -#import "TestFlight.h" -#import #import "ATConnect.h" @interface MPAppDelegate () @@ -36,9 +34,9 @@ @implementation MPAppDelegate + (void)initialize { - + [MPiOSConfig get]; - + #ifdef DEBUG [PearlLogger get].autoprintLevel = PearlLogLevelDebug; //[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")]; @@ -46,7 +44,7 @@ } + (MPAppDelegate *)get { - + return (MPAppDelegate *)[super get]; } @@ -57,54 +55,55 @@ } - (void)showGuide { - + [self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self]; - + [TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide]; } - (void)export { - + [PearlAlert showNotice: - @"This will export all your site names.\n\n" - @"You can open the export with a text editor to get an overview of all your sites.\n\n" - @"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes." + @"This will export all your site names.\n\n" + @"You can open the export with a text editor to get an overview of all your sites.\n\n" + @"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes." tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { [PearlAlert showAlertWithTitle:@"Reveal Passwords?" message: - @"Would you like to make all your passwords visible in the export?\n\n" - @"A safe export will only include your stored passwords, in an encrypted manner, " - @"making the result safe from falling in the wrong hands.\n\n" - @"If all your passwords are shown and somebody else finds the export, " - @"they could gain access to all your sites!" - viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { - if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0) - // Safe Export - [self exportShowPasswords:NO]; - if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1) - // Safe Export - [self exportShowPasswords:YES]; - } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil]; + @"Would you like to make all your passwords visible in the export?\n\n" + @"A safe export will only include your stored passwords, in an encrypted manner, " + @"making the result safe from falling in the wrong hands.\n\n" + @"If all your passwords are shown and somebody else finds the export, " + @"they could gain access to all your sites!" + viewStyle:UIAlertViewStyleDefault initAlert:nil + tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { + if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0) + // Safe Export + [self exportShowPasswords:NO]; + if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1) + // Safe Export + [self exportShowPasswords:YES]; + } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil]; } otherTitles:nil]; } - (void)exportShowPasswords:(BOOL)showPasswords { - + NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords]; NSString *message; if (showPasswords) message = @"Export of my Master Password sites with passwords visible.\n\nREMINDER: Make sure nobody else sees this file!\n"; else message = @"Backup of my Master Password sites.\n"; - + NSDateFormatter *exportDateFormatter = [NSDateFormatter new]; [exportDateFormatter setDateFormat:@"'Master Password sites ('yyyy'-'MM'-'DD').mpsites'"]; - + MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init]; [composer setMailComposeDelegate:self]; [composer setSubject:@"Master Password site export"]; [composer setMessageBody:message isHTML:NO]; [composer addAttachmentData:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain" - fileName:[exportDateFormatter stringFromDate:[NSDate date]]]; + fileName:[exportDateFormatter stringFromDate:[NSDate date]]]; [self.window.rootViewController presentModalViewController:composer animated:YES]; } @@ -112,29 +111,29 @@ [PearlAlert showAlertWithTitle:@"Changing Master Password" message: - @"This will allow you to log in with a different master password.\n\n" - @"Note that you will only see the sites and passwords for the master password you log in with.\n" - @"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." + @"This will allow you to log in with a different master password.\n\n" + @"Note that you will only see the sites and passwords for the master password you log in with.\n" + @"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." viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { - if (buttonIndex == [alert cancelButtonIndex]) - return; + if (buttonIndex == [alert cancelButtonIndex]) + return; - self.activeUser.keyID = nil; - [self signOut]; + self.activeUser.keyID = nil; + [self signOut]; - [TestFlight passCheckpoint:MPTestFlightCheckpointChangeMP]; - } - cancelTitle:[PearlStrings get].commonButtonAbort - otherTitles:[PearlStrings get].commonButtonContinue, nil]; + [TestFlight passCheckpoint:MPTestFlightCheckpointChangeMP]; + } + cancelTitle:[PearlStrings get].commonButtonAbort + otherTitles:[PearlStrings get].commonButtonContinue, nil]; } #pragma mark - PearlConfigDelegate - (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value { - + [self checkConfig]; } @@ -142,10 +141,10 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - + [[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"]; [[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"]; - + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ #ifndef DEBUG @try { @@ -205,12 +204,12 @@ } #endif }); - + @try { NSString *apptentiveAPIKey = [self apptentiveAPIKey]; if ([apptentiveAPIKey length]) { dbg(@"Initializing Apptentive"); - + ATConnect *connection = [ATConnect sharedConnection]; [connection setApiKey:apptentiveAPIKey]; [connection setShouldTakeScreenshot:NO]; @@ -220,58 +219,58 @@ @catch (NSException *exception) { err(@"Apptentive: %@", exception); } - - UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)]; + + UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)]; [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault]; [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone]; [[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys: - [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor, - [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor, - [NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset, - [UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont, - nil]]; - - UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)]; - UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)]; + [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor, + [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor, + [NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset, + [UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont, + nil]]; + + UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)]; + UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)]; [[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; [[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; [[UIBarButtonItem appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys: - [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor, - [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor, - [NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset, - [UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont, - nil] - forState:UIControlStateNormal]; - - UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)]; + [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor, + [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor, + [NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset, + [UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont, + nil] + forState:UIControlStateNormal]; + + UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)]; [[UISearchBar appearance] setBackgroundImage:toolBarImage]; [[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault]; - + /* - UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)]; - UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)]; - UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"]; - - [[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal]; - [[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal]; - [[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal]; - - UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)]; - UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)]; - UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"]; - UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"]; - UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"]; - - [[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; - [[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault]; - - [[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; - [[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; - [[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/ +UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)]; +UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)]; +UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"]; + +[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal]; +[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal]; +[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal]; + +UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)]; +UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)]; +UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"]; +UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"]; +UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"]; + +[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; +[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault]; + +[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; +[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; +[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/ [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil usingBlock:^(NSNotification *note) { @@ -281,7 +280,7 @@ usingBlock:^(NSNotification *note) { [self checkConfig]; }]; - + #ifdef ADHOC [PearlAlert showAlertWithTitle:@"Welcome, tester!" message: @"Thank you for taking the time to test Master Password.\n\n" @@ -293,56 +292,59 @@ viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil]; #endif - - [[UIApplication sharedApplication] setStatusBarHidden:NO - withAnimation:UIStatusBarAnimationSlide]; - + + [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]; + return [super application:application didFinishLaunchingWithOptions:launchOptions]; } - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { - - __autoreleasing NSError *error; + + __autoreleasing NSError *error; __autoreleasing NSURLResponse *response; - NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] - returningResponse:&response error:&error]; + NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] + returningResponse:&response error:&error]; if (error) - err(@"While reading imported sites from %@: %@", url, error); + err(@"While reading imported sites from %@: %@", url, error); if (!importedSitesData) return NO; - + NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; [PearlAlert showAlertWithTitle:@"Import Password" message: - @"Enter the master password for this export:" + @"Enter the master password for this export:" viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock: ^(UIAlertView *alert, NSInteger buttonIndex) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text - askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) { - __block BOOL confirmation = NO; - - dispatch_group_t confirmationGroup = dispatch_group_create(); - dispatch_group_enter(confirmationGroup); - dispatch_async(dispatch_get_main_queue(), ^{ - [PearlAlert showAlertWithTitle:@"Import Sites?" - message:PearlString(@"Import %d sites, overwriting %d existing sites?", importCount, deleteCount) - viewStyle:UIAlertViewStyleDefault - initAlert:nil - tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { - if (buttonIndex_ != [alert_ cancelButtonIndex]) - confirmation = YES; - - dispatch_group_leave(confirmationGroup); - } - cancelTitle:[PearlStrings get].commonButtonCancel - otherTitles:@"Import", nil]; - }); - dispatch_group_wait(confirmationGroup, DISPATCH_TIME_FOREVER); - - return confirmation; - }]; - + askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) { + __block BOOL confirmation = NO; + + dispatch_group_t confirmationGroup = dispatch_group_create(); + dispatch_group_enter(confirmationGroup); + dispatch_async(dispatch_get_main_queue(), ^{ + [PearlAlert showAlertWithTitle:@"Import Sites?" + message:PearlString( + @"Import %d sites, overwriting %d existing sites?", + importCount, deleteCount) + viewStyle:UIAlertViewStyleDefault + initAlert:nil + tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { + if (buttonIndex_ + != [alert_ cancelButtonIndex]) + confirmation = YES; + + dispatch_group_leave(confirmationGroup); + } + cancelTitle:[PearlStrings get].commonButtonCancel + otherTitles:@"Import", nil]; + }); + dispatch_group_wait( + confirmationGroup, DISPATCH_TIME_FOREVER); + + return confirmation; + }]; + switch (result) { case MPImportResultSuccess: case MPImportResultCancelled: @@ -359,53 +361,53 @@ } }); } - cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil]; - + cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil]; + return YES; } - (void)applicationDidBecomeActive:(UIApplication *)application { - + if ([[MPiOSConfig get].showQuickStart boolValue]) [self showGuide]; [TestFlight passCheckpoint:MPTestFlightCheckpointActivated]; - + [super applicationDidBecomeActive:application]; } - (void)applicationDidEnterBackground:(UIApplication *)application { - + [[LocalyticsSession sharedLocalyticsSession] close]; [[LocalyticsSession sharedLocalyticsSession] upload]; - + [super applicationDidEnterBackground:application]; } - (void)applicationWillEnterForeground:(UIApplication *)application { - + [[LocalyticsSession sharedLocalyticsSession] resume]; [[LocalyticsSession sharedLocalyticsSession] upload]; - + [super applicationWillEnterForeground:application]; } - (void)applicationWillTerminate:(UIApplication *)application { - + [self saveContext]; - + [TestFlight passCheckpoint:MPTestFlightCheckpointTerminated]; - + [[LocalyticsSession sharedLocalyticsSession] close]; [[LocalyticsSession sharedLocalyticsSession] upload]; - + [super applicationWillTerminate:application]; } - (void)applicationWillResignActive:(UIApplication *)application { - + [self saveContext]; - + if (![[MPiOSConfig get].rememberLogin boolValue]) [self signOut]; @@ -416,70 +418,71 @@ - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { - + if (error) - err(@"Error composing mail message: %@", error); - + err(@"Error composing mail message: %@", error); + switch (result) { case MFMailComposeResultSaved: case MFMailComposeResultSent: break; - + case MFMailComposeResultFailed: - [PearlAlert showError:@"A problem occurred while sending the message." tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { - if (buttonIndex == [alert firstOtherButtonIndex]) - return; - } otherTitles:@"Retry", nil]; + [PearlAlert showError:@"A problem occurred while sending the message." + tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { + if (buttonIndex == [alert firstOtherButtonIndex]) + return; + } otherTitles:@"Retry", nil]; return; case MFMailComposeResultCancelled: break; } - + [controller dismissModalViewControllerAnimated:YES]; } #pragma mark - UbiquityStoreManagerDelegate - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - + [super ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; - + if (![[MPConfig get].iCloudDecided boolValue]) { if (!iCloudEnabled) { [PearlAlert showAlertWithTitle:@"iCloud" message: - @"iCloud is now disabled.\n\n" - @"It is highly recommended you enable iCloud." - viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { - if (buttonIndex == [alert firstOtherButtonIndex] + 0) { - [PearlAlert showAlertWithTitle:@"About iCloud" - message: - @"iCloud is Apple's solution for saving your data in \"the cloud\" " - @"and making sure your other iPhones, iPads and Macs are in sync.\n\n" - @"For Master Password, that means your sites are available on all your " - @"Apple devices, and you always have a backup of them in case " - @"you loose one or need to restore.\n\n" - @"Because of the way Master Password works, it doesn't need to send your " - @"site's passwords to Apple. Only their names are saved to make it easier " - @"for you to find the site you need. For some sites you may have set " - @"a user-specified password: these are sent to iCloud after being encrypted " - @"with your master password.\n\n" - @"Apple can never see any of your passwords." - viewStyle:UIAlertViewStyleDefault - initAlert:nil - tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { - [self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; - } - cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil]; - return; - } - - [MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES]; - if (buttonIndex == [alert cancelButtonIndex]) - return; - if (buttonIndex == [alert firstOtherButtonIndex] + 1) - [manager useiCloudStore:YES alertUser:NO]; - } cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil]; + @"iCloud is now disabled.\n\n" + @"It is highly recommended you enable iCloud." + viewStyle:UIAlertViewStyleDefault initAlert:nil + tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { + if (buttonIndex == [alert firstOtherButtonIndex] + 0) { + [PearlAlert showAlertWithTitle:@"About iCloud" + message: + @"iCloud is Apple's solution for saving your data in \"the cloud\" " + @"and making sure your other iPhones, iPads and Macs are in sync.\n\n" + @"For Master Password, that means your sites are available on all your " + @"Apple devices, and you always have a backup of them in case " + @"you loose one or need to restore.\n\n" + @"Because of the way Master Password works, it doesn't need to send your " + @"site's passwords to Apple. Only their names are saved to make it easier " + @"for you to find the site you need. For some sites you may have set " + @"a user-specified password: these are sent to iCloud after being encrypted " + @"with your master password.\n\n" + @"Apple can never see any of your passwords." + viewStyle:UIAlertViewStyleDefault + initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { + [self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; + } + cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil]; + return; + } + + [MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES]; + if (buttonIndex == [alert cancelButtonIndex]) + return; + if (buttonIndex == [alert firstOtherButtonIndex] + 1) + [manager useiCloudStore:YES alertUser:NO]; + } cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil]; } } } @@ -488,17 +491,17 @@ - (NSDictionary *)testFlightInfo { - + static NSDictionary *testFlightInfo = nil; if (testFlightInfo == nil) testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL: - [[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]]; - + [[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]]; + return testFlightInfo; } - (NSString *)testFlightToken { - + return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]); } @@ -507,17 +510,17 @@ - (NSDictionary *)crashlyticsInfo { - + static NSDictionary *crashlyticsInfo = nil; if (crashlyticsInfo == nil) crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL: - [[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]]; - + [[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]]; + return crashlyticsInfo; } - (NSString *)crashlyticsAPIKey { - + return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]); } @@ -526,17 +529,17 @@ - (NSDictionary *)apptentiveInfo { - + static NSDictionary *apptentiveInfo = nil; if (apptentiveInfo == nil) apptentiveInfo = [[NSDictionary alloc] initWithContentsOfURL: - [[NSBundle mainBundle] URLForResource:@"Apptentive" withExtension:@"plist"]]; - + [[NSBundle mainBundle] URLForResource:@"Apptentive" withExtension:@"plist"]]; + return apptentiveInfo; } - (NSString *)apptentiveAPIKey { - + return NSNullToNil([[self apptentiveInfo] valueForKeyPath:@"API Key"]); } @@ -545,17 +548,17 @@ - (NSDictionary *)localyticsInfo { - + static NSDictionary *localyticsInfo = nil; if (localyticsInfo == nil) localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL: - [[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]]; - + [[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]]; + return localyticsInfo; } - (NSString *)localyticsKey { - + #ifdef DEBUG return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]); #else diff --git a/MasterPassword/iOS/MPGuideViewController.m b/MasterPassword/iOS/MPGuideViewController.m index 5031a3c9..0dadc753 100644 --- a/MasterPassword/iOS/MPGuideViewController.m +++ b/MasterPassword/iOS/MPGuideViewController.m @@ -7,28 +7,27 @@ // #import "MPGuideViewController.h" -#import "MPAppDelegate.h" @implementation MPGuideViewController @synthesize scrollView; - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - + return (interfaceOrientation == UIInterfaceOrientationPortrait); } - (void)viewDidLoad { - + [super viewDidLoad]; [self.scrollView autoSizeContent]; } - (void)viewWillDisappear:(BOOL)animated { - + [super viewWillDisappear:animated]; - + [MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO]; } diff --git a/MasterPassword/iOS/MPMainViewController.h b/MasterPassword/iOS/MPMainViewController.h index 8405b747..be690637 100644 --- a/MasterPassword/iOS/MPMainViewController.h +++ b/MasterPassword/iOS/MPMainViewController.h @@ -10,28 +10,28 @@ #import "MPElementEntity.h" #import "MPSearchDelegate.h" -@interface MPMainViewController : UIViewController +@interface MPMainViewController : UIViewController -@property (strong, nonatomic) MPElementEntity *activeElement; +@property (strong, nonatomic) MPElementEntity *activeElement; @property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController; -@property (weak, nonatomic) IBOutlet UITextField *contentField; -@property (weak, nonatomic) IBOutlet UIButton *typeButton; -@property (weak, nonatomic) IBOutlet UIWebView *helpView; -@property (weak, nonatomic) IBOutlet UILabel *siteName; -@property (weak, nonatomic) IBOutlet UILabel *passwordCounter; -@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer; -@property (weak, nonatomic) IBOutlet UIButton *passwordEdit; -@property (weak, nonatomic) IBOutlet UIView *contentContainer; -@property (weak, nonatomic) IBOutlet UIView *helpContainer; -@property (weak, nonatomic) IBOutlet UIView *contentTipContainer; -@property (weak, nonatomic) IBOutlet UIView *alertContainer; -@property (weak, nonatomic) IBOutlet UILabel *alertTitle; -@property (weak, nonatomic) IBOutlet UITextView *alertBody; -@property (weak, nonatomic) IBOutlet UILabel *contentTipBody; -@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon; -@property (weak, nonatomic) IBOutlet UIView *searchTipContainer; -@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer; -@property (weak, nonatomic) IBOutlet UIView *typeTipContainer; +@property (weak, nonatomic) IBOutlet UITextField *contentField; +@property (weak, nonatomic) IBOutlet UIButton *typeButton; +@property (weak, nonatomic) IBOutlet UIWebView *helpView; +@property (weak, nonatomic) IBOutlet UILabel *siteName; +@property (weak, nonatomic) IBOutlet UILabel *passwordCounter; +@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer; +@property (weak, nonatomic) IBOutlet UIButton *passwordEdit; +@property (weak, nonatomic) IBOutlet UIView *contentContainer; +@property (weak, nonatomic) IBOutlet UIView *helpContainer; +@property (weak, nonatomic) IBOutlet UIView *contentTipContainer; +@property (weak, nonatomic) IBOutlet UIView *alertContainer; +@property (weak, nonatomic) IBOutlet UILabel *alertTitle; +@property (weak, nonatomic) IBOutlet UITextView *alertBody; +@property (weak, nonatomic) IBOutlet UILabel *contentTipBody; +@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon; +@property (weak, nonatomic) IBOutlet UIView *searchTipContainer; +@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer; +@property (weak, nonatomic) IBOutlet UIView *typeTipContainer; @property (copy) void (^contentTipCleanup)(BOOL finished); diff --git a/MasterPassword/iOS/MPMainViewController.m b/MasterPassword/iOS/MPMainViewController.m index e59c05f5..b843961b 100644 --- a/MasterPassword/iOS/MPMainViewController.m +++ b/MasterPassword/iOS/MPMainViewController.m @@ -12,8 +12,6 @@ #import "MPAppDelegate_Store.h" #import "ATConnect.h" -#import - @interface MPMainViewController (Private) @@ -51,51 +49,51 @@ #pragma mark - View lifecycle - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - + return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown; } - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { - + [self setHelpHidden:![self isHelpVisible] animated:NO]; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - + if ([[segue identifier] isEqualToString:@"MP_Main_ChooseType"]) ((MPTypeViewController *)[segue destinationViewController]).delegate = self; } - (void)viewWillAppear:(BOOL)animated { - + [super viewWillAppear:animated]; - + if (![MPAppDelegate get].activeUser) [self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:self]; if (self.activeElement.user != [MPAppDelegate get].activeUser) - self.activeElement = nil; + self.activeElement = nil; self.searchDisplayController.searchBar.text = nil; - - self.searchTipContainer.alpha = 0; + + self.searchTipContainer.alpha = 0; self.actionsTipContainer.alpha = 0; - self.typeTipContainer.alpha = 0; - + self.typeTipContainer.alpha = 0; + [self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated]; [self updateAnimated:animated]; } - (void)viewDidAppear:(BOOL)animated { - + if ([[MPiOSConfig get].firstRun boolValue]) [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ self.actionsTipContainer.alpha = 1; - } completion:^(BOOL finished) { + } completion:^(BOOL finished) { if (finished) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView animateWithDuration:0.2f animations:^{ self.actionsTipContainer.alpha = 0; - } completion:^(BOOL finished_) { + } completion:^(BOOL finished_) { if (![self.activeElement.name length]) [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ self.searchTipContainer.alpha = 1; @@ -104,26 +102,26 @@ }); } }]; - + [[MPAppDelegate get] checkConfig]; - + [super viewDidAppear:animated]; } - (void)viewDidLoad { - + self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]]; - + self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize]; - - self.alertBody.text = nil; + + self.alertBody.text = nil; self.contentTipEditIcon.hidden = YES; - + [super viewDidLoad]; } - (void)viewDidUnload { - + [self setContentField:nil]; [self setTypeButton:nil]; [self setSearchResultsController:nil]; @@ -147,35 +145,35 @@ } - (void)updateAnimated:(BOOL)animated { - + if (animated) { [UIView animateWithDuration:0.3f animations:^{ [self updateAnimated:NO]; }]; return; } - + [self setHelpChapter:self.activeElement? @"2": @"1"]; self.siteName.text = self.activeElement.name; - - self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0; + + self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0; self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0; - self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0; - + self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0; + [self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type) forState:UIControlStateNormal]; self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0; - + self.contentField.enabled = NO; - + if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) - self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *) self.activeElement).counter); - + self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter); + self.contentField.text = @""; if (self.activeElement.name && ![self.activeElement isDeleted]) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSString *description = [self.activeElement.content description]; - + dispatch_async(dispatch_get_main_queue(), ^{ self.contentField.text = description; }); @@ -183,26 +181,26 @@ } - (BOOL)isHelpVisible { - + return self.helpContainer.frame.origin.y == 216; } - (void)toggleHelpAnimated:(BOOL)animated { - + [self setHelpHidden:[self isHelpVisible] animated:animated]; } - (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated { - + dispatch_async(dispatch_get_main_queue(), ^{ [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ if (hidden) { - self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44); - self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height); + self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44); + self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height); [MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES]; } else { - self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175); - self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216); + self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175); + self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216); [MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO]; } }]; @@ -210,9 +208,9 @@ } - (void)setHelpChapter:(NSString *)chapter { - + [TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]]; - + dispatch_async(dispatch_get_main_queue(), ^{ NSURL *url = [NSURL URLWithString:[@"#" stringByAppendingString:chapter] relativeToURL:[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]; @@ -221,35 +219,35 @@ } - (void)webViewDidFinishLoad:(UIWebView *)webView { - + NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString: - PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))]; + PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))]; if (error.length) - err(@"helpView.setClass: %@", error); + err(@"helpView.setClass: %@", error); } - (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon { - + dispatch_async(dispatch_get_main_queue(), ^{ if (self.contentTipCleanup) self.contentTipCleanup(NO); - + self.contentTipBody.text = message; - self.contentTipCleanup = ^(BOOL finished) { - icon.hidden = YES; + self.contentTipCleanup = ^(BOOL finished) { + icon.hidden = YES; self.contentTipCleanup = nil; }; - + icon.hidden = NO; [UIView animateWithDuration:0.3f animations:^{ self.contentTipContainer.alpha = 1; - } completion:^(BOOL finished) { + } completion:^(BOOL finished) { if (finished) { dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { [UIView animateWithDuration:0.2f animations:^{ self.contentTipContainer.alpha = 0; - } completion:self.contentTipCleanup]; + } completion:self.contentTipCleanup]; }); } }]; @@ -257,7 +255,7 @@ } - (void)showAlertWithTitle:(NSString *)title message:(NSString *)message { - + dispatch_async(dispatch_get_main_queue(), ^{ self.alertTitle.text = title; NSRange scrollRange = NSMakeRange(self.alertBody.text.length, message.length); @@ -266,7 +264,7 @@ else self.alertBody.text = message; [self.alertBody scrollRangeToVisible:scrollRange]; - + [UIView animateWithDuration:0.3f animations:^{ self.alertContainer.alpha = 1; }]; @@ -276,119 +274,118 @@ #pragma mark - Protocols - (IBAction)copyContent { - + if (!self.activeElement) return; - + [UIPasteboard generalPasteboard].string = [self.activeElement.content description]; - + [self showContentTip:@"Copied!" withIcon:nil]; - + [TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard]; } - (IBAction)incrementPasswordCounter { - + if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) - // Not of a type that supports a password counter. + // Not of a type that supports a password counter. return; - + [self changeElementWithWarning: - @"You are incrementing the site's password counter.\n\n" - @"If you continue, a new password will be generated for this site. " - @"You will then need to update your account's old password to this newly generated password.\n\n" - @"You can reset the counter by holding down on this button." + @"You are incrementing the site's password counter.\n\n" + @"If you continue, a new password will be generated for this site. " + @"You will then need to update your account's old password to this newly generated password.\n\n" + @"You can reset the counter by holding down on this button." do:^{ - ++((MPElementGeneratedEntity *) self.activeElement).counter; + ++((MPElementGeneratedEntity *)self.activeElement).counter; }]; - + [TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter]; } - (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender { - + if (sender.state != UIGestureRecognizerStateBegan) - // Only fire when the gesture was first detected. + // Only fire when the gesture was first detected. return; if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) - // Not of a type that supports a password counter. + // Not of a type that supports a password counter. return; if (((MPElementGeneratedEntity *)self.activeElement).counter == 1) - // Counter has initial value, no point resetting. + // Counter has initial value, no point resetting. return; - + [self changeElementWithWarning: - @"You are resetting the site's password counter.\n\n" - @"If you continue, the site's password will change back to its original value. " - @"You will then need to update your account's password back to this original value." + @"You are resetting the site's password counter.\n\n" + @"If you continue, the site's password will change back to its original value. " + @"You will then need to update your account's password back to this original value." do:^{ - ((MPElementGeneratedEntity *) self.activeElement).counter = 1; + ((MPElementGeneratedEntity *)self.activeElement).counter = 1; }]; - + [TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter]; } - (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; { - + [PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault - initAlert:nil - tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { - if (buttonIndex == [alert cancelButtonIndex]) - return; - - [self changeElementWithoutWarningDo:task]; - } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil]; + initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { + if (buttonIndex == [alert cancelButtonIndex]) + return; + + [self changeElementWithoutWarningDo:task]; + } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil]; } - (void)changeElementWithoutWarningDo:(void (^)(void))task; { - + // Update element, keeping track of the old password. NSString *oldPassword = [self.activeElement.content description]; task(); NSString *newPassword = [self.activeElement.content description]; [[MPAppDelegate get] saveContext]; [self updateAnimated:YES]; - + // Show new and old password. if ([oldPassword length] && ![oldPassword isEqualToString:newPassword]) [self showAlertWithTitle:@"Password Changed!" message:PearlString(@"The password for %@ has changed.\n\n" - @"IMPORTANT:\n" - @"Don't forget to update the site with your new password! " - @"Your old password was:\n" - @"%@", self.activeElement.name, oldPassword)]; + @"IMPORTANT:\n" + @"Don't forget to update the site with your new password! " + @"Your old password was:\n" + @"%@", self.activeElement.name, oldPassword)]; } - (IBAction)editPassword { - + if (self.activeElement.type & MPElementTypeClassStored) { self.contentField.enabled = YES; [self.contentField becomeFirstResponder]; } - + [TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword]; } - (IBAction)closeAlert { - + [UIView animateWithDuration:0.3f animations:^{ self.alertContainer.alpha = 0; - } completion:^(BOOL finished) { + } completion:^(BOOL finished) { if (finished) self.alertBody.text = nil; }]; - + [TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert]; } - (IBAction)action:(id)sender { - + [PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { if (buttonIndex == [sheet cancelButtonIndex]) return; - + switch (buttonIndex - [sheet firstOtherButtonIndex]) { case 0: { [self toggleHelpAnimated:YES]; @@ -427,68 +424,70 @@ break; } } - + [TestFlight passCheckpoint:MPTestFlightCheckpointAction]; } - cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles: + cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles: [self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil]; } - (MPElementType)selectedType { - + return self.activeElement.type; } - (void)didSelectType:(MPElementType)type { - + [self changeElementWithWarning: - @"You are about to change the type of this password.\n\n" - @"If you continue, the password for this site will change. " - @"You will need to update your account's old password to the new one." + @"You are about to change the type of this password.\n\n" + @"If you continue, the password for this site will change. " + @"You will need to update your account's old password to the new one." do:^{ // Update password type. if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type)) - // Type requires a different class of element. Recreate the element. + // Type requires a different class of element. Recreate the element. [[MPAppDelegate managedObjectContext] performBlockAndWait:^{ - MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) + MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType( + type) inManagedObjectContext:[MPAppDelegate managedObjectContext]]; - newElement.name = self.activeElement.name; - newElement.user = self.activeElement.user; - newElement.uses = self.activeElement.uses; + newElement.name = self.activeElement.name; + newElement.user = self.activeElement.user; + newElement.uses = self.activeElement.uses; newElement.lastUsed = self.activeElement.lastUsed; - + [[MPAppDelegate managedObjectContext] deleteObject:self.activeElement]; self.activeElement = newElement; }]; - + self.activeElement.type = type; - - [TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]]; - + + [TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType( + type)]]; + if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length]) [self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon]; }]; } - (void)didSelectElement:(MPElementEntity *)element { - + [self closeAlert]; - + if (element) { self.activeElement = element; if ([self.activeElement use] == 1) [self showAlertWithTitle:@"New Site" message: - PearlString(@"You've just created a password for %@.\n\n" - @"IMPORTANT:\n" - @"Go to %@ and set or change the password for your account to the password above.\n" - @"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.", - self.activeElement.name, self.activeElement.name)]; + PearlString(@"You've just created a password for %@.\n\n" + @"IMPORTANT:\n" + @"Go to %@ and set or change the password for your account to the password above.\n" + @"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.", + self.activeElement.name, self.activeElement.name)]; [[MPAppDelegate get] saveContext]; - + if ([[MPiOSConfig get].firstRun boolValue]) [UIView animateWithDuration:0.5f animations:^{ self.typeTipContainer.alpha = 1; - } completion:^(BOOL finished) { + } completion:^(BOOL finished) { if (finished) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView animateWithDuration:0.2f animations:^{ @@ -497,53 +496,53 @@ }); } }]; - + [self.searchDisplayController setActive:NO animated:YES]; self.searchDisplayController.searchBar.text = self.activeElement.name; - + [TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement]; [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement]; } - + [self updateAnimated:YES]; } - (BOOL)textFieldShouldReturn:(UITextField *)textField { - + if (textField == self.contentField) [self.contentField resignFirstResponder]; - + return YES; } - (void)textFieldDidEndEditing:(UITextField *)textField { - + if (textField == self.contentField) { self.contentField.enabled = NO; if (![self.activeElement isKindOfClass:[MPElementStoredEntity class]]) - // Not of a type whose content can be edited. + // Not of a type whose content can be edited. return; - - if ([((MPElementStoredEntity *) self.activeElement).content isEqual:self.contentField.text]) - // Content hasn't changed. + + if ([((MPElementStoredEntity *)self.activeElement).content isEqual:self.contentField.text]) + // Content hasn't changed. return; - + [self changeElementWithoutWarningDo:^{ - ((MPElementStoredEntity *) self.activeElement).content = self.contentField.text; + ((MPElementStoredEntity *)self.activeElement).content = self.contentField.text; }]; } } - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { - + if (navigationType == UIWebViewNavigationTypeLinkClicked) { [TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink]; - + [[UIApplication sharedApplication] openURL:[request URL]]; return NO; } - + return YES; } diff --git a/MasterPassword/iOS/MPPreferencesViewController.h b/MasterPassword/iOS/MPPreferencesViewController.h index 0e9897d7..e9b0fc3b 100644 --- a/MasterPassword/iOS/MPPreferencesViewController.h +++ b/MasterPassword/iOS/MPPreferencesViewController.h @@ -9,11 +9,11 @@ #import #import "IASKAppSettingsViewController.h" -@interface MPPreferencesViewController : UITableViewController +@interface MPPreferencesViewController : UITableViewController -@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView; -@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate; -@property (weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch; +@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView; +@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate; +@property (weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch; @property (weak, nonatomic) IBOutlet UITableViewCell *exportCell; @property (weak, nonatomic) IBOutlet UITableViewCell *changeMPCell; diff --git a/MasterPassword/iOS/MPPreferencesViewController.m b/MasterPassword/iOS/MPPreferencesViewController.m index 01fa795b..d655c3a8 100644 --- a/MasterPassword/iOS/MPPreferencesViewController.m +++ b/MasterPassword/iOS/MPPreferencesViewController.m @@ -24,25 +24,25 @@ - (void)viewDidLoad { - + self.avatarTemplate.hidden = YES; for (int a = 0; a < MPAvatarCount; ++a) { UIButton *avatar = [self.avatarTemplate clone]; avatar.togglesSelectionInSuperview = YES; - avatar.tag = a; - avatar.hidden = NO; - avatar.center = CGPointMake( - self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a, - self.avatarTemplate.center.y); + avatar.tag = a; + avatar.hidden = NO; + avatar.center = CGPointMake( + self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a, + self.avatarTemplate.center.y); [avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)] forState:UIControlStateNormal]; - avatar.layer.cornerRadius = avatar.bounds.size.height / 2; - avatar.layer.shadowColor = [UIColor blackColor].CGColor; + avatar.layer.cornerRadius = avatar.bounds.size.height / 2; + avatar.layer.shadowColor = [UIColor blackColor].CGColor; avatar.layer.shadowOpacity = 1; - avatar.layer.shadowRadius = 5; - avatar.backgroundColor = [UIColor clearColor]; + avatar.layer.shadowRadius = 5; + avatar.backgroundColor = [UIColor clearColor]; [avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) { if (highlighted || selected) @@ -54,32 +54,33 @@ if (selected) [MPAppDelegate get].activeUser.avatar = (unsigned)avatar.tag; } options:0]; - avatar.selected = (a == [MPAppDelegate get].activeUser.avatar); + avatar.selected = (a == [MPAppDelegate get].activeUser.avatar); } [super viewDidLoad]; } - (void)viewWillAppear:(BOOL)animated { - + [self.avatarsView autoSizeContent]; [self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { - if (subview.tag && ((UIControl *) subview).selected) { + if (subview.tag && ((UIControl *)subview).selected) { [self.avatarsView setContentOffset:CGPointMake(subview.center.x - self.avatarsView.bounds.size.width / 2, 0) animated:animated]; } } recurse:NO]; self.savePasswordSwitch.on = [MPAppDelegate get].activeUser.saveKey; - + [super viewWillAppear:animated]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - + return (interfaceOrientation == UIInterfaceOrientationPortrait); } - (void)viewDidUnload { + [self setAvatarsView:nil]; [self setAvatarTemplate:nil]; [self setAvatarsView:nil]; @@ -92,13 +93,14 @@ #pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - + UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath]; if (cell == self.exportCell) [[MPAppDelegate get] export]; - else if (cell == self.changeMPCell) - [[MPAppDelegate get] changeMP]; + else + if (cell == self.changeMPCell) + [[MPAppDelegate get] changeMP]; [tableView deselectRowAtIndexPath:indexPath animated:YES]; } @@ -106,7 +108,7 @@ #pragma mark - IASKSettingsDelegate - (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender { - + while ([self.navigationController.viewControllers containsObject:sender]) [self.navigationController popViewControllerAnimated:YES]; } @@ -114,7 +116,7 @@ #pragma mark - IBActions - (IBAction)didToggleSwitch:(UISwitch *)sender { - + if (([MPAppDelegate get].activeUser.saveKey = sender.on)) [[MPAppDelegate get] storeSavedKeyFor:[MPAppDelegate get].activeUser]; else diff --git a/MasterPassword/iOS/MPSearchDelegate.h b/MasterPassword/iOS/MPSearchDelegate.h index 71725733..be40352f 100644 --- a/MasterPassword/iOS/MPSearchDelegate.h +++ b/MasterPassword/iOS/MPSearchDelegate.h @@ -9,21 +9,21 @@ #import #import "MPElementEntity.h" -@protocol MPSearchResultsDelegate +@protocol MPSearchResultsDelegate - (void)didSelectElement:(MPElementEntity *)element; @end -@interface MPSearchDelegate : NSObject +@interface MPSearchDelegate : NSObject -@property (strong, nonatomic) NSDateFormatter *dateFormatter; +@property (strong, nonatomic) NSDateFormatter *dateFormatter; @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController; -@property (strong, nonatomic) NSString *query; -@property (strong, nonatomic) UILabel *tipView; +@property (strong, nonatomic) NSString *query; +@property (strong, nonatomic) UILabel *tipView; @property (weak, nonatomic) IBOutlet id delegate; @property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController; -@property (weak, nonatomic) IBOutlet UIView *searchTipContainer; +@property (weak, nonatomic) IBOutlet UIView *searchTipContainer; @end diff --git a/MasterPassword/iOS/MPSearchDelegate.m b/MasterPassword/iOS/MPSearchDelegate.m index 28bf5414..d1130b7c 100644 --- a/MasterPassword/iOS/MPSearchDelegate.m +++ b/MasterPassword/iOS/MPSearchDelegate.m @@ -26,52 +26,53 @@ @synthesize searchTipContainer; - (id)init { - + if (!([super init])) return nil; - - self.dateFormatter = [NSDateFormatter new]; + + self.dateFormatter = [NSDateFormatter new]; self.dateFormatter.dateStyle = NSDateFormatterShortStyle; - self.query = @""; - - NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; - fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]]; - self.fetchedResultsController = [PearlLazy lazyObjectLoadedFrom:^id{ + self.query = @""; + + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; + fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]]; + self.fetchedResultsController = [PearlLazy lazyObjectLoadedFrom:^id { NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[MPAppDelegate managedObjectContext] - sectionNameKeyPath:nil cacheName:nil]; - controller.delegate = self; - + sectionNameKeyPath:nil cacheName:nil]; + controller.delegate = self; + return controller; }]; - - self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)]; - self.tipView.textAlignment = UITextAlignmentCenter; - self.tipView.backgroundColor = [UIColor clearColor]; - self.tipView.textColor = [UIColor lightTextColor]; - self.tipView.shadowColor = [UIColor blackColor]; - self.tipView.shadowOffset = CGSizeMake(0, -1); - self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin; - self.tipView.numberOfLines = 0; - self.tipView.font = [UIFont systemFontOfSize:14]; + + self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)]; + self.tipView.textAlignment = UITextAlignmentCenter; + self.tipView.backgroundColor = [UIColor clearColor]; + self.tipView.textColor = [UIColor lightTextColor]; + self.tipView.shadowColor = [UIColor blackColor]; + self.tipView.shadowOffset = CGSizeMake(0, -1); + self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight + | UIViewAutoresizingFlexibleBottomMargin; + self.tipView.numberOfLines = 0; + self.tipView.font = [UIFont systemFontOfSize:14]; self.tipView.text = - @"Tip:\n" - @"Name your sites by their domain name:\n" - @"apple.com, twitter.com\n\n" - @"For email accounts, use the address:\n" - @"john@apple.com, john@gmail.com"; - + @"Tip:\n" + @"Name your sites by their domain name:\n" + @"apple.com, twitter.com\n\n" + @"For email accounts, use the address:\n" + @"john@apple.com, john@gmail.com"; + return self; } - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { - + UITableView *tableView = self.searchDisplayController.searchResultsTableView; for (NSInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) { NSInteger rowCount = [self tableView:tableView numberOfRowsInSection:section]; if (!rowCount) continue; - + if (rowCount == 1) [self tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]]; break; @@ -79,64 +80,64 @@ } - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { - + [TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch]; - + [self.delegate didSelectElement:nil]; } - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { - + if (searchBar.searchResultsButtonSelected && !searchText.length) searchBar.text = @" "; - - self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; if (!self.query) self.query = @""; } - (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller { - + controller.searchBar.prompt = @"Enter the site's name:"; - + [UIView animateWithDuration:0.2f animations:^{ self.searchTipContainer.alpha = 0; }]; } - (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller { - + controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @""; - self.query = @""; + self.query = @""; } - (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller { - - controller.searchBar.prompt = nil; + + controller.searchBar.prompt = nil; controller.searchBar.searchResultsButtonSelected = NO; } - (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView { - + tableView.backgroundColor = [UIColor blackColor]; - tableView.separatorStyle = UITableViewCellSeparatorStyleNone; - tableView.rowHeight = 48.0f; + tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + tableView.rowHeight = 48.0f; } - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { - + if (!controller.active) return NO; - + assert(self.query); - + self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@", - self.query, self.query, NilToNSNull([MPAppDelegate get].activeUser)]; - + self.query, self.query, NilToNSNull([MPAppDelegate get].activeUser)]; + NSError *error; if (![self.fetchedResultsController performFetch:&error]) - err(@"Couldn't fetch elements: %@", error); - + err(@"Couldn't fetch elements: %@", error); + NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews; NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1; UIView *overlay = [subviews count] > overlayIndex? [subviews objectAtIndex:overlayIndex]: nil; @@ -146,7 +147,7 @@ [self.tipView removeFromSuperview]; [overlay addSubview:self.tipView]; } - + return YES; } @@ -204,16 +205,16 @@ //} - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { - + [self.searchDisplayController.searchResultsTableView reloadData]; // [self.searchDisplayController.searchResultsTableView endUpdates]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - + NSArray *sections = [self.fetchedResultsController sections]; NSUInteger sectionCount = [sections count]; - + if ([self.query length]) { __block BOOL hasExactQueryMatch = NO; [sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { @@ -225,129 +226,130 @@ } }]; if (hasExactQueryMatch) - *stop = YES; + *stop = YES; }]; if (!hasExactQueryMatch) - // Add a section for "new site". + // Add a section for "new site". ++sectionCount; } - + return (NSInteger)sectionCount; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - + NSArray *sections = [self.fetchedResultsController sections]; if (section < (NSInteger)[sections count]) return (NSInteger)[[sections objectAtIndex:(unsigned)section] numberOfObjects]; - + return 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"]; - + UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]]; - backgroundImageView.frame = CGRectMake(-5, 0, 330, 34); + backgroundImageView.frame = CGRectMake(-5, 0, 330, 34); backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f); + backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f); UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)]; [backgroundView addSubview:backgroundImageView]; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - - cell.backgroundView = backgroundView; - cell.textLabel.backgroundColor = [UIColor clearColor]; - cell.textLabel.textColor = [UIColor whiteColor]; + + cell.backgroundView = backgroundView; + cell.textLabel.backgroundColor = [UIColor clearColor]; + cell.textLabel.textColor = [UIColor whiteColor]; cell.detailTextLabel.backgroundColor = [UIColor clearColor]; - cell.detailTextLabel.textColor = [UIColor lightGrayColor]; - cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - cell.clipsToBounds = YES; + cell.detailTextLabel.textColor = [UIColor lightGrayColor]; + cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + cell.clipsToBounds = YES; } - + [self configureCell:cell inTableView:tableView atIndexPath:indexPath]; - + return cell; } - (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath { - + if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) { MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; - - cell.textLabel.text = element.name; + + cell.textLabel.text = element.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"Used %d times, last on %@", - element.uses, [self.dateFormatter stringFromDate:element.lastUsed]]; + element.uses, [self.dateFormatter stringFromDate:element.lastUsed]]; } else { // "New" section - cell.textLabel.text = self.query; + cell.textLabel.text = self.query; cell.detailTextLabel.text = @"Create a new site."; } } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - + if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) [self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]]; - + else { // "New" section. NSString *siteName = self.query; [PearlAlert showAlertWithTitle:@"New Site" message:PearlString(@"Do you want to create a new site named:\n%@", siteName) viewStyle:UIAlertViewStyleDefault - initAlert:nil - tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - - if (buttonIndex == [alert cancelButtonIndex]) - return; - - [self.fetchedResultsController.managedObjectContext performBlock:^{ - MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class]) - inManagedObjectContext:self.fetchedResultsController.managedObjectContext]; - assert([element isKindOfClass:ClassFromMPElementType(element.type)]); - assert([MPAppDelegate get].activeUser.keyID); - - element.name = siteName; - element.user = [MPAppDelegate get].activeUser; - - dispatch_async(dispatch_get_main_queue(), ^{ - [self.delegate didSelectElement:element]; - }); - }]; - } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; + initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + if (buttonIndex == [alert cancelButtonIndex]) + return; + + [self.fetchedResultsController.managedObjectContext performBlock:^{ + MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( + [MPElementGeneratedEntity class]) + inManagedObjectContext:self.fetchedResultsController.managedObjectContext]; + assert([element isKindOfClass:ClassFromMPElementType(element.type)]); + assert([MPAppDelegate get].activeUser.keyID); + + element.name = siteName; + element.user = [MPAppDelegate get].activeUser; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate didSelectElement:element]; + }); + }]; + } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; } } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - + if (section < (NSInteger)[[self.fetchedResultsController sections] count]) return [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name]; - + return @""; } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { - + return [self.fetchedResultsController sectionIndexTitles]; } - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { - + return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index]; } -- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle +forRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) { if (editingStyle == UITableViewCellEditingStyleDelete) [self.fetchedResultsController.managedObjectContext performBlock:^{ MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; [self.fetchedResultsController.managedObjectContext deleteObject:element]; - + [TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement]; }]; } diff --git a/MasterPassword/iOS/MPTypeViewController.h b/MasterPassword/iOS/MPTypeViewController.h index 1e3c1371..d019087e 100644 --- a/MasterPassword/iOS/MPTypeViewController.h +++ b/MasterPassword/iOS/MPTypeViewController.h @@ -8,7 +8,7 @@ #import -@protocol MPTypeDelegate +@protocol MPTypeDelegate - (void)didSelectType:(MPElementType)type; diff --git a/MasterPassword/iOS/MPTypeViewController.m b/MasterPassword/iOS/MPTypeViewController.m index 8e1ec781..1f6f617b 100644 --- a/MasterPassword/iOS/MPTypeViewController.m +++ b/MasterPassword/iOS/MPTypeViewController.m @@ -22,16 +22,16 @@ #pragma mark - View lifecycle - (void)viewWillAppear:(BOOL)animated { - + self.recommendedTipContainer.alpha = 0; } - (void)viewDidAppear:(BOOL)animated { - + if ([[MPiOSConfig get].firstRun boolValue]) [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ self.recommendedTipContainer.alpha = 1; - } completion:^(BOOL finished) { + } completion:^(BOOL finished) { if (finished) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView animateWithDuration:0.2f animations:^{ @@ -40,26 +40,26 @@ }); } }]; - + [super viewDidAppear:animated]; } - (void)viewDidLoad { - + self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]]; - + [super viewDidLoad]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - + return YES; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - + UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; - + if ([delegate respondsToSelector:@selector(selectedType)]) if ([delegate selectedType] == [self typeAtIndexPath:indexPath]) [cell enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { @@ -67,24 +67,24 @@ UIImageView *imageView = ((UIImageView *)subview); if (!imageView.highlightedImage) imageView.highlightedImage = [imageView.image highlightedImage]; - imageView.highlighted = YES; + imageView.highlighted = YES; *stop = YES; } } recurse:NO]; - + return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - + assert(self.navigationController.topViewController == self); - + [delegate didSelectType:[self typeAtIndexPath:indexPath]]; [self.navigationController popViewControllerAnimated:YES]; } - (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath { - + switch (indexPath.section) { case 0: { // Generated @@ -103,12 +103,12 @@ return MPElementTypeGeneratedBasic; case 6: return MPElementTypeGeneratedPIN; - + default: Throw(@"Unsupported row: %d, when selecting generated element type.", indexPath.row); } } - + case 1: { // Stored switch (indexPath.row) { @@ -118,18 +118,19 @@ return MPElementTypeStoredPersonal; case 2: return MPElementTypeStoredDevicePrivate; - + default: Throw(@"Unsupported row: %d, when selecting stored element type.", indexPath.row); } } - + default: Throw(@"Unsupported section: %d, when selecting element type.", indexPath.section); } } - (void)viewDidUnload { + [self setRecommendedTipContainer:nil]; [super viewDidUnload]; } diff --git a/MasterPassword/iOS/MPUnlockViewController.h b/MasterPassword/iOS/MPUnlockViewController.h index ac924e30..267cc621 100644 --- a/MasterPassword/iOS/MPUnlockViewController.h +++ b/MasterPassword/iOS/MPUnlockViewController.h @@ -8,20 +8,20 @@ #import -@interface MPUnlockViewController : UIViewController +@interface MPUnlockViewController : UIViewController -@property (weak, nonatomic) IBOutlet UIImageView *spinner; -@property (weak, nonatomic) IBOutlet UITextField *passwordField; -@property (weak, nonatomic) IBOutlet UIView *passwordView; +@property (weak, nonatomic) IBOutlet UIImageView *spinner; +@property (weak, nonatomic) IBOutlet UITextField *passwordField; +@property (weak, nonatomic) IBOutlet UIView *passwordView; @property (weak, nonatomic) IBOutlet UIScrollView *avatarsView; -@property (weak, nonatomic) IBOutlet UILabel *nameLabel; -@property (weak, nonatomic) IBOutlet UILabel *oldNameLabel; -@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate; -@property (weak, nonatomic) IBOutlet UILabel *deleteTip; -@property (weak, nonatomic) IBOutlet UIView *passwordTipView; -@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel; +@property (weak, nonatomic) IBOutlet UILabel *nameLabel; +@property (weak, nonatomic) IBOutlet UILabel *oldNameLabel; +@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate; +@property (weak, nonatomic) IBOutlet UILabel *deleteTip; +@property (weak, nonatomic) IBOutlet UIView *passwordTipView; +@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel; -@property(nonatomic, strong) UIColor *avatarShadowColor; +@property (nonatomic, strong) UIColor *avatarShadowColor; - (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender; diff --git a/MasterPassword/iOS/MPUnlockViewController.m b/MasterPassword/iOS/MPUnlockViewController.m index 927ba778..86c078a1 100644 --- a/MasterPassword/iOS/MPUnlockViewController.m +++ b/MasterPassword/iOS/MPUnlockViewController.m @@ -15,8 +15,8 @@ @interface MPUnlockViewController () -@property(strong, nonatomic) MPUserEntity *selectedUser; -@property(strong, nonatomic) NSMutableDictionary *avatarToUser; +@property (strong, nonatomic) MPUserEntity *selectedUser; +@property (strong, nonatomic) NSMutableDictionary *avatarToUser; @end @@ -40,29 +40,29 @@ // } completion:nil]; - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - + return (interfaceOrientation == UIInterfaceOrientationPortrait); } - (void)viewDidLoad { - + self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3]; - - self.passwordField.text = nil; + + self.passwordField.text = nil; self.avatarsView.decelerationRate = UIScrollViewDecelerationRateFast; - self.avatarsView.clipsToBounds = NO; + self.avatarsView.clipsToBounds = NO; self.nameLabel.layer.cornerRadius = 5; - self.avatarTemplate.hidden = YES; - self.spinner.alpha = 0; - self.passwordTipView.alpha = 0; + self.avatarTemplate.hidden = YES; + self.spinner.alpha = 0; + self.passwordTipView.alpha = 0; [self updateLayoutAnimated:NO allowScroll:YES completion:nil]; - + [super viewDidLoad]; } - (void)viewDidUnload { - + [self setSpinner:nil]; [self setPasswordField:nil]; [self setPasswordView:nil]; @@ -76,24 +76,24 @@ } - (void)viewWillAppear:(BOOL)animated { - + self.selectedUser = nil; [self updateUsers]; - + [super viewWillAppear:animated]; } - (void)viewDidAppear:(BOOL)animated { - - [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated ? UIStatusBarAnimationSlide : UIStatusBarAnimationNone]; - + + [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone]; + [super viewDidAppear:animated]; } - (void)viewWillDisappear:(BOOL)animated { - - [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:animated ? UIStatusBarAnimationSlide : UIStatusBarAnimationNone]; - + + [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone]; + [super viewWillDisappear:animated]; } @@ -102,11 +102,11 @@ NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]]; NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil]; - + // Clean up avatars. for (UIView *subview in [self.avatarsView subviews]) if ([[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) - // This subview is a former avatar. + // This subview is a former avatar. [subview removeFromSuperview]; [self.avatarToUser removeAllObjects]; @@ -117,12 +117,12 @@ // Scroll view's content changed, update its content size. [self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil]; - + [self updateLayoutAnimated:YES allowScroll:YES completion:nil]; } - (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user { - + [avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) { if (highlighted || selected) avatar.backgroundColor = self.avatarTemplate.backgroundColor; @@ -130,43 +130,45 @@ avatar.backgroundColor = [UIColor clearColor]; } options:0]; [avatar onSelect:^(BOOL selected) { - self.selectedUser = selected ? user : nil; + self.selectedUser = selected? user: nil; if (user) [self didToggleUserSelection]; - else if (selected) - [self didSelectNewUserAvatar:avatar]; + else + if (selected) + [self didSelectNewUserAvatar:avatar]; } options:0]; avatar.togglesSelectionInSuperview = YES; - avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y); - avatar.hidden = NO; - avatar.layer.cornerRadius = avatar.bounds.size.height / 2; - avatar.layer.shadowColor = [UIColor blackColor].CGColor; - avatar.layer.shadowOpacity = 1; - avatar.layer.shadowRadius = 20; - avatar.layer.masksToBounds = NO; - avatar.backgroundColor = [UIColor clearColor]; - + avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y); + avatar.hidden = NO; + avatar.layer.cornerRadius = avatar.bounds.size.height / 2; + avatar.layer.shadowColor = [UIColor blackColor].CGColor; + avatar.layer.shadowOpacity = 1; + avatar.layer.shadowRadius = 20; + avatar.layer.masksToBounds = NO; + avatar.backgroundColor = [UIColor clearColor]; + dbg(@"User: %@, avatar: %d", user.name, user.avatar); avatar.tag = user.avatar; [avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)] - forState:UIControlStateNormal]; - + forState:UIControlStateNormal]; + [self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]]; - + if (self.selectedUser && user == self.selectedUser) avatar.selected = YES; - + return avatar; } - (void)didToggleUserSelection { - + if (!self.selectedUser) [self.passwordField resignFirstResponder]; - else if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) { - [self dismissModalViewControllerAnimated:YES]; - return; - } + else + if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) { + [self dismissModalViewControllerAnimated:YES]; + return; + } [self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) { if (finished) @@ -176,104 +178,105 @@ } - (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar { - + [PearlAlert showAlertWithTitle:@"New User" message:@"Enter your name:" viewStyle:UIAlertViewStylePlainTextInput initAlert:^(UIAlertView *alert, UITextField *firstField) { firstField.autocapitalizationType = UITextAutocapitalizationTypeWords; - firstField.autocorrectionType = UITextAutocorrectionTypeYes; - firstField.spellCheckingType = UITextSpellCheckingTypeYes; - firstField.keyboardType = UIKeyboardTypeAlphabet; + firstField.autocorrectionType = UITextAutocorrectionTypeYes; + firstField.spellCheckingType = UITextSpellCheckingTypeYes; + firstField.keyboardType = UIKeyboardTypeAlphabet; } tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { newUserAvatar.selected = NO; - + if (buttonIndex == [alert cancelButtonIndex]) return; - + MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) - inManagedObjectContext:[MPAppDelegate managedObjectContext]]; - newUser.name = [alert textFieldAtIndex:0].text; + inManagedObjectContext:[MPAppDelegate managedObjectContext]]; + newUser.name = [alert textFieldAtIndex:0].text; self.selectedUser = newUser; - + [self updateUsers]; } - cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil]; + cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil]; } - (void)updateLayoutAnimated:(BOOL)animated allowScroll:(BOOL)allowScroll completion:(void (^)(BOOL finished))completion { if (animated) { - self.oldNameLabel.text = self.nameLabel.text; + self.oldNameLabel.text = self.nameLabel.text; self.oldNameLabel.alpha = 1; - self.nameLabel.alpha = 0; - + self.nameLabel.alpha = 0; + [UIView animateWithDuration:0.5f animations:^{ [self updateLayoutAnimated:NO allowScroll:allowScroll completion:nil]; - + self.oldNameLabel.alpha = 0; - self.nameLabel.alpha = 1; + self.nameLabel.alpha = 1; } completion:^(BOOL finished) { if (completion) completion(finished); }]; return; } - - if (self.selectedUser && !self.passwordView.alpha) { - self.passwordView.alpha = 1; - self.avatarsView.center = CGPointMake(160, 100); - self.avatarsView.scrollEnabled = NO; - self.nameLabel.center = CGPointMake(160, 84); - self.nameLabel.backgroundColor = [UIColor blackColor]; - self.oldNameLabel.center = self.nameLabel.center; - self.avatarShadowColor = [UIColor whiteColor]; - self.deleteTip.alpha = 0; - } else if (!self.selectedUser && self.passwordView.alpha == 1) { - self.passwordView.alpha = 0; - self.avatarsView.center = CGPointMake(160, 240); - self.avatarsView.scrollEnabled = YES; - self.nameLabel.center = CGPointMake(160, 296); - self.nameLabel.backgroundColor = [UIColor clearColor]; - self.oldNameLabel.center = self.nameLabel.center; - self.avatarShadowColor = [UIColor lightGrayColor]; - self.deleteTip.alpha = [self.avatarToUser count] > 2? 1: 0; - } - MPUserEntity *targetedUser = self.selectedUser; - UIButton *selectedAvatar = [self avatarForUser:self.selectedUser]; - UIButton *targetedAvatar = selectedAvatar; + if (self.selectedUser && !self.passwordView.alpha) { + self.passwordView.alpha = 1; + self.avatarsView.center = CGPointMake(160, 100); + self.avatarsView.scrollEnabled = NO; + self.nameLabel.center = CGPointMake(160, 84); + self.nameLabel.backgroundColor = [UIColor blackColor]; + self.oldNameLabel.center = self.nameLabel.center; + self.avatarShadowColor = [UIColor whiteColor]; + self.deleteTip.alpha = 0; + } else + if (!self.selectedUser && self.passwordView.alpha == 1) { + self.passwordView.alpha = 0; + self.avatarsView.center = CGPointMake(160, 240); + self.avatarsView.scrollEnabled = YES; + self.nameLabel.center = CGPointMake(160, 296); + self.nameLabel.backgroundColor = [UIColor clearColor]; + self.oldNameLabel.center = self.nameLabel.center; + self.avatarShadowColor = [UIColor lightGrayColor]; + self.deleteTip.alpha = [self.avatarToUser count] > 2? 1: 0; + } + + MPUserEntity *targetedUser = self.selectedUser; + UIButton *selectedAvatar = [self avatarForUser:self.selectedUser]; + UIButton *targetedAvatar = selectedAvatar; if (!targetedAvatar) { targetedAvatar = [self findTargetedAvatar]; - targetedUser = [self userForAvatar:targetedAvatar]; + targetedUser = [self userForAvatar:targetedAvatar]; } - + [self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { if (![[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) - // This subview is not one of the user avatars. + // This subview is not one of the user avatars. return; UIButton *avatar = (UIButton *)subview; BOOL isTargeted = avatar == targetedAvatar; avatar.userInteractionEnabled = isTargeted; - avatar.alpha = isTargeted ? 1 : self.selectedUser ? 0.1 : 0.4; - + avatar.alpha = isTargeted? 1: self.selectedUser? 0.1: 0.4; + [self updateAvatarShadowColor:avatar isTargeted:isTargeted]; } recurse:NO]; - + if (allowScroll) { CGPoint targetContentOffset = CGPointMake(MAX(0, targetedAvatar.center.x - self.avatarsView.bounds.size.width / 2), - self.avatarsView.contentOffset.y); + self.avatarsView.contentOffset.y); if (!CGPointEqualToPoint(self.avatarsView.contentOffset, targetContentOffset)) [self.avatarsView setContentOffset:targetContentOffset animated:animated]; } - self.nameLabel.text = targetedUser ? targetedUser.name : @"New User"; - self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds, - [self.nameLabel.text sizeWithFont:self.nameLabel.font - constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100) - lineBreakMode:self.nameLabel.lineBreakMode].height); + self.nameLabel.text = targetedUser? targetedUser.name: @"New User"; + self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds, + [self.nameLabel.text sizeWithFont:self.nameLabel.font + constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100) + lineBreakMode:self.nameLabel.lineBreakMode].height); self.oldNameLabel.bounds = self.nameLabel.bounds; if (completion) completion(YES); @@ -290,45 +293,47 @@ } - (void)tryMasterPassword { - + [self setSpinnerActive:YES]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text]; - + dispatch_async(dispatch_get_main_queue(), ^{ if (unlocked) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long) (NSEC_PER_SEC * 0.5f)), dispatch_get_main_queue(), ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 0.5f)), dispatch_get_main_queue(), ^{ [self dismissModalViewControllerAnimated:YES]; }); - } else if (self.passwordField.text.length) - [self setPasswordTip:@"Incorrect password."]; - + } else + if (self.passwordField.text.length) + [self setPasswordTip:@"Incorrect password."]; + [self setSpinnerActive:NO]; }); }); } - (UIButton *)findTargetedAvatar { - + CGFloat xOfMiddle = self.avatarsView.contentOffset.x + self.avatarsView.bounds.size.width / 2; - return (UIButton *) [PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, self.avatarsView.contentOffset.y) ofArray:self.avatarsView.subviews]; + return (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, self.avatarsView.contentOffset.y) + ofArray:self.avatarsView.subviews]; } - (UIButton *)avatarForUser:(MPUserEntity *)user { - + __block UIButton *avatar = nil; if (user) [self.avatarToUser enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if (obj == user) avatar = [key nonretainedObjectValue]; }]; - + return avatar; } - (MPUserEntity *)userForAvatar:(UIButton *)avatar { - + return NSNullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]); } @@ -336,16 +341,16 @@ PearlMainThread(^{ CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; - rotate.toValue = [NSNumber numberWithDouble:2 * M_PI]; + rotate.toValue = [NSNumber numberWithDouble:2 * M_PI]; rotate.duration = 5.0; if (active) { rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; - rotate.fromValue = [NSNumber numberWithFloat:0]; - rotate.repeatCount = MAXFLOAT; + rotate.fromValue = [NSNumber numberWithFloat:0]; + rotate.repeatCount = MAXFLOAT; } else { rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; - rotate.repeatCount = 1; + rotate.repeatCount = 1; } [self.spinner.layer removeAnimationForKey:@"rotation"]; @@ -363,48 +368,49 @@ } - (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted { - + if (targeted) { if (![avatar.layer animationForKey:@"targetedShadow"]) { CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"]; - toShadowColorAnimation.toValue = (__bridge id) (avatar.selected? self.avatarTemplate.backgroundColor: [UIColor whiteColor]).CGColor; + toShadowColorAnimation.toValue = (__bridge id)(avatar.selected? self.avatarTemplate.backgroundColor + : [UIColor whiteColor]).CGColor; toShadowColorAnimation.beginTime = 0.0f; - toShadowColorAnimation.duration = 0.5f; - toShadowColorAnimation.fillMode = kCAFillModeForwards; - + toShadowColorAnimation.duration = 0.5f; + toShadowColorAnimation.fillMode = kCAFillModeForwards; + CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; - toShadowOpacityAnimation.toValue = PearlFloat(0.2); + toShadowOpacityAnimation.toValue = PearlFloat(0.2); toShadowOpacityAnimation.duration = 0.5f; - + CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; - pulseShadowOpacityAnimation.fromValue = PearlFloat(0.2); - pulseShadowOpacityAnimation.toValue = PearlFloat(0.6); - pulseShadowOpacityAnimation.beginTime = 0.5f; - pulseShadowOpacityAnimation.duration = 2.0f; + pulseShadowOpacityAnimation.fromValue = PearlFloat(0.2); + pulseShadowOpacityAnimation.toValue = PearlFloat(0.6); + pulseShadowOpacityAnimation.beginTime = 0.5f; + pulseShadowOpacityAnimation.duration = 2.0f; pulseShadowOpacityAnimation.autoreverses = YES; - pulseShadowOpacityAnimation.repeatCount = MAXFLOAT; - + pulseShadowOpacityAnimation.repeatCount = MAXFLOAT; + CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation, nil]; - group.duration = MAXFLOAT; - + group.duration = MAXFLOAT; + [avatar.layer removeAnimationForKey:@"inactiveShadow"]; [avatar.layer addAnimation:group forKey:@"targetedShadow"]; } } else { if ([avatar.layer animationForKey:@"targetedShadow"]) { CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"]; - toShadowColorAnimation.toValue = (__bridge id) [UIColor blackColor].CGColor; + toShadowColorAnimation.toValue = (__bridge id)[UIColor blackColor].CGColor; toShadowColorAnimation.duration = 0.5f; - + CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; - toShadowOpacityAnimation.toValue = PearlFloat(1); + toShadowOpacityAnimation.toValue = PearlFloat(1); toShadowOpacityAnimation.duration = 0.5f; - + CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, nil]; - group.duration = 0.5f; - + group.duration = 0.5f; + [avatar.layer removeAnimationForKey:@"targetedShadow"]; [avatar.layer addAnimation:group forKey:@"inactiveShadow"]; } @@ -419,91 +425,95 @@ } - (BOOL)textFieldShouldReturn:(UITextField *)textField { - + [textField resignFirstResponder]; - + [self setSpinnerActive:YES]; if (self.selectedUser.keyID) [self tryMasterPassword]; - + else [PearlAlert showAlertWithTitle:@"New Master Password" message:@"Please confirm the spelling of this new master password." viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { - [self setSpinnerActive:NO]; - - if (buttonIndex == [alert cancelButtonIndex]) - return; - - if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) { - [PearlAlert showAlertWithTitle:@"Incorrect Master Password" - message: - @"The password you entered doesn't match with the master password you tried to use. " - @"You've probably mistyped one of them.\n\n" - @"Give it another try." - viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil]; - return; - } - - [self tryMasterPassword]; - } - cancelTitle:[PearlStrings get].commonButtonCancel - otherTitles:[PearlStrings get].commonButtonContinue, nil]; - - + [self setSpinnerActive:NO]; + + if (buttonIndex == [alert cancelButtonIndex]) + return; + + if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) { + [PearlAlert showAlertWithTitle:@"Incorrect Master Password" + message: + @"The password you entered doesn't match with the master password you tried to use. " + @"You've probably mistyped one of them.\n\n" + @"Give it another try." + viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil + cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil]; + return; + } + + [self tryMasterPassword]; + } + cancelTitle:[PearlStrings get].commonButtonCancel + otherTitles:[PearlStrings get].commonButtonContinue, nil]; + + return YES; } #pragma mark - UIScrollViewDelegate -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { - +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint *)targetContentOffset { + CGFloat xOfMiddle = targetContentOffset->x + scrollView.bounds.size.width / 2; - UIButton *middleAvatar = (UIButton *) [PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, targetContentOffset->y) ofArray:scrollView.subviews]; + UIButton *middleAvatar = (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, targetContentOffset->y) + ofArray:scrollView.subviews]; *targetContentOffset = CGPointMake(middleAvatar.center.x - scrollView.bounds.size.width / 2, targetContentOffset->y); - + [self updateLayoutAnimated:NO allowScroll:NO completion:nil]; // [self scrollToAvatar:middleAvatar animated:YES]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { - + [self updateLayoutAnimated:YES allowScroll:YES completion:nil]; // [self scrollToAvatar:middleAvatar animated:YES]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { - + [self updateLayoutAnimated:NO allowScroll:NO completion:nil]; } #pragma mark - IBActions - (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender { - + if (sender.state != UIGestureRecognizerStateBegan) return; - + if (self.selectedUser) return; - + MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]]; if (!targetedUser) return; - + [PearlAlert showAlertWithTitle:@"Delete User" message: - PearlString(@"Do you want to delete all record of the following user?\n\n%@", targetedUser.name) + PearlString(@"Do you want to delete all record of the following user?\n\n%@", + targetedUser.name) viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { - if (buttonIndex == [alert cancelButtonIndex]) - return; - - [[MPAppDelegate get].managedObjectContext deleteObject:targetedUser]; - [[MPAppDelegate get] saveContext]; - - [self updateUsers]; - } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Delete", nil]; + if (buttonIndex == [alert cancelButtonIndex]) + return; + + [[MPAppDelegate get].managedObjectContext deleteObject:targetedUser]; + [[MPAppDelegate get] saveContext]; + + [self updateUsers]; + } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Delete", nil]; } @end diff --git a/MasterPassword/iOS/MPiOSConfig.m b/MasterPassword/iOS/MPiOSConfig.m index 4e37992f..dd003ed8 100644 --- a/MasterPassword/iOS/MPiOSConfig.m +++ b/MasterPassword/iOS/MPiOSConfig.m @@ -6,27 +6,25 @@ // Copyright (c) 2012 Lyndir. All rights reserved. // -#import "MPiOSConfig.h" - @implementation MPiOSConfig @dynamic helpHidden, showQuickStart; - (id)init { - - if(!(self = [super init])) + + if (!(self = [super init])) return self; - + [self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)), - [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)), - @"510296984", NSStringFromSelector(@selector(iTunesID)), - nil]]; - + [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)), + [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)), + @"510296984", NSStringFromSelector(@selector(iTunesID)), + nil]]; + return self; } + (MPiOSConfig *)get { - + return (MPiOSConfig *)[super get]; } diff --git a/MasterPassword/iOS/MasterPassword-Prefix.pch b/MasterPassword/iOS/MasterPassword-Prefix.pch index 3a7b386c..0324e764 100644 --- a/MasterPassword/iOS/MasterPassword-Prefix.pch +++ b/MasterPassword/iOS/MasterPassword-Prefix.pch @@ -11,12 +11,14 @@ #import "Pearl-Prefix.pch" #ifdef __OBJC__ - #import - #import - #import - #import "TestFlight.h" +#import +#import +#import + +#import "TestFlight.h" + +#import "MPTypes.h" +#import "MPiOSConfig.h" - #import "MPTypes.h" - #import "MPiOSConfig.h" #endif diff --git a/MasterPassword/iOS/main.m b/MasterPassword/iOS/main.m index 9af0575b..db12e85d 100644 --- a/MasterPassword/iOS/main.m +++ b/MasterPassword/iOS/main.m @@ -8,8 +8,8 @@ #import "MPAppDelegate.h" -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { + @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([MPAppDelegate class])); }