2
0

AppCode code formatting.

This commit is contained in:
Maarten Billemont 2012-06-08 23:46:13 +02:00
parent d59f77720c
commit a8bf74a925
38 changed files with 1132 additions and 1101 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit e55ef6876ee26f61a7cd2c075fc1e7a942016de0 Subproject commit 009482a08a2a05e9856c2158c1040d01aeedb5ff

View File

@ -15,17 +15,17 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"Saved Master Password", (__bridge id)kSecAttrService, @"Saved Master Password", (__bridge id)kSecAttrService,
user.name, (__bridge id)kSecAttrAccount, user.name, (__bridge id)kSecAttrAccount,
nil] nil]
matches:nil]; matches:nil];
} }
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user { - (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)]; NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (key) if (key)
inf(@"Found key (for: %@) in keychain.", user.name); inf(@"Found key (for: %@) in keychain.", user.name);
else { else {
user.saveKey = NO; user.saveKey = NO;
@ -44,11 +44,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
inf(@"Updating key in keychain."); inf(@"Updating key in keychain.");
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user) [PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id) kSecValueData, self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id) kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif #endif
nil]]; nil]];
} }
} }
} }
@ -72,7 +72,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
- (void)signOut { - (void)signOut {
self.key = nil; self.key = nil;
self.activeUser = nil; self.activeUser = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self]; [[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. // Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
if (!user.saveKey) if (!user.saveKey)
// Key should not be stored in keychain. Delete it. // Key should not be stored in keychain. Delete it.
[self forgetSavedKeyFor:user]; [self forgetSavedKeyFor:user];
else if (!tryKey) { else
// Key should be saved in keychain. Load it. if (!tryKey) {
if ((tryKey = [self loadSavedKeyFor:user])) // Key should be saved in keychain. Load it.
if (![user.keyID isEqual:keyIDForKey(tryKey)]) { if ((tryKey = [self loadSavedKeyFor:user]))
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect. if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
tryKey = nil; // Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
[self forgetSavedKeyFor:user]; tryKey = nil;
[self forgetSavedKeyFor:user];
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif #endif
} }
} }
// Method 3: Check the given master password string. // Method 3: Check the given master password string.
if (!tryKey) { if (!tryKey) {
@ -117,9 +118,9 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if (![user.keyID isEqual:keyIDForKey(tryKey)]) { if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
tryKey = nil; tryKey = nil;
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif #endif
} }
} }
@ -132,7 +133,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
[self storeSavedKeyFor:user]; [self storeSavedKeyFor:user];
} }
user.lastUsed = [NSDate date]; user.lastUsed = [NSDate date];
self.activeUser = user; self.activeUser = user;
[[MPAppDelegate_Shared get] saveContext]; [[MPAppDelegate_Shared get] saveContext];

View File

@ -9,13 +9,14 @@
#import "MPEntities.h" #import "MPEntities.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
@interface MPAppDelegate_Shared : PearlAppDelegate @interface MPAppDelegate_Shared : PearlAppDelegate
#else #else
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate> @interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
#endif #endif
@property (strong, nonatomic) MPUserEntity *activeUser; @property (strong, nonatomic) MPUserEntity *activeUser;
@property (strong, nonatomic) NSData *key; @property (strong, nonatomic) NSData *key;
+ (MPAppDelegate_Shared *)get; + (MPAppDelegate_Shared *)get;

View File

@ -14,7 +14,7 @@
@synthesize activeUser; @synthesize activeUser;
+ (MPAppDelegate_Shared *)get { + (MPAppDelegate_Shared *)get {
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
return (MPAppDelegate_Shared *)[UIApplication sharedApplication].delegate; return (MPAppDelegate_Shared *)[UIApplication sharedApplication].delegate;
#elif defined (__MAC_OS_X_VERSION_MIN_REQUIRED) #elif defined (__MAC_OS_X_VERSION_MIN_REQUIRED)

View File

@ -18,7 +18,7 @@ typedef enum {
MPImportResultInternalError, MPImportResultInternalError,
} MPImportResult; } MPImportResult;
@interface MPAppDelegate_Shared (Store) <UbiquityStoreManagerDelegate> @interface MPAppDelegate_Shared (Store)<UbiquityStoreManagerDelegate>
+ (NSManagedObjectContext *)managedObjectContext; + (NSManagedObjectContext *)managedObjectContext;
+ (NSManagedObjectModel *)managedObjectModel; + (NSManagedObjectModel *)managedObjectModel;

View File

@ -15,79 +15,80 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#pragma mark - Core Data setup #pragma mark - Core Data setup
+ (NSManagedObjectContext *)managedObjectContext { + (NSManagedObjectContext *)managedObjectContext {
return [[self get] managedObjectContext]; return [[self get] managedObjectContext];
} }
+ (NSManagedObjectModel *)managedObjectModel { + (NSManagedObjectModel *)managedObjectModel {
return [[self get] managedObjectModel]; return [[self get] managedObjectModel];
} }
- (NSManagedObjectModel *)managedObjectModel { - (NSManagedObjectModel *)managedObjectModel {
static NSManagedObjectModel *managedObjectModel = nil; static NSManagedObjectModel *managedObjectModel = nil;
if (managedObjectModel) if (managedObjectModel)
return managedObjectModel; return managedObjectModel;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"]; NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"];
return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
} }
- (NSManagedObjectContext *)managedObjectContext { - (NSManagedObjectContext *)managedObjectContext {
static NSManagedObjectContext *managedObjectContext = nil; static NSManagedObjectContext *managedObjectContext = nil;
if (managedObjectContext) if (managedObjectContext)
return managedObjectContext; return managedObjectContext;
return [PearlLazy lazyObjectLoadedFrom:^id{ return [PearlLazy lazyObjectLoadedFrom:^id {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
assert(coordinator); assert(coordinator);
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext performBlockAndWait:^{ [managedObjectContext performBlockAndWait:^{
managedObjectContext.persistentStoreCoordinator = coordinator; managedObjectContext.persistentStoreCoordinator = coordinator;
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
}]; }];
return managedObjectContext; return managedObjectContext;
}]; }];
} }
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// Start loading the store. // Start loading the store.
[self storeManager]; [self storeManager];
return [PearlLazy lazyObjectLoadedFrom:^id{ return [PearlLazy lazyObjectLoadedFrom:^id {
// Wait until the storeManager is ready. // 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]; [NSThread sleepForTimeInterval:0.1];
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
isReady = [self storeManager].isReady; isReady = [self storeManager].isReady;
}); });
} }
assert([self storeManager].isReady); assert([self storeManager].isReady);
return [self storeManager].persistentStoreCoordinator; return [self storeManager].persistentStoreCoordinator;
}]; }];
} }
- (UbiquityStoreManager *)storeManager { - (UbiquityStoreManager *)storeManager {
static UbiquityStoreManager *storeManager = nil; static UbiquityStoreManager *storeManager = nil;
if (storeManager) if (storeManager)
return storeManager; return storeManager;
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel] storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"] localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared" containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSPersistentStoreFileProtectionKey] additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSPersistentStoreFileProtectionKey]
#else #else
additionalStoreOptions:nil additionalStoreOptions:nil
#endif #endif
]; ];
storeManager.delegate = self; storeManager.delegate = self;
#ifdef DEBUG #ifdef DEBUG
storeManager.hardResetEnabled = YES; storeManager.hardResetEnabled = YES;
@ -95,9 +96,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication] queue:nil object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[storeManager checkiCloudStatus]; [storeManager checkiCloudStatus];
}]; }];
#else #else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
object:[NSApplication sharedApplication] queue:nil object:[NSApplication sharedApplication] queue:nil
@ -108,9 +109,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
object:[UIApplication sharedApplication] queue:nil object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[self saveContext]; [self saveContext];
}]; }];
#else #else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
object:[NSApplication sharedApplication] queue:nil object:[NSApplication sharedApplication] queue:nil
@ -118,52 +119,53 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[self saveContext]; [self saveContext];
}]; }];
#endif #endif
return storeManager; return storeManager;
} }
- (void)saveContext { - (void)saveContext {
[self.managedObjectContext performBlock:^{ [self.managedObjectContext performBlock:^{
NSError *error = nil; NSError *error = nil;
if ([self.managedObjectContext hasChanges]) if ([self.managedObjectContext hasChanges])
if (![self.managedObjectContext save:&error]) if (![self.managedObjectContext save:&error])
err(@"While saving context: %@", error); err(@"While saving context: %@", error);
}]; }];
} }
#pragma mark - UbiquityStoreManagerDelegate #pragma mark - UbiquityStoreManagerDelegate
- (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm { - (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm {
return self.managedObjectContext; return self.managedObjectContext;
} }
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
dbg(@"[StoreManager] %@", message); dbg(@"[StoreManager] %@", message);
} }
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
// manager.iCloudEnabled is more reliable (eg. iOS tampers with didSwitch a bit) // manager.iCloudEnabled is more reliable (eg. iOS tampers with didSwitch a bit)
iCloudEnabled = manager.iCloudEnabled; iCloudEnabled = manager.iCloudEnabled;
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:iCloudEnabled? MPTestFlightCheckpointCloudEnabled: MPTestFlightCheckpointCloudDisabled]; [TestFlight passCheckpoint:iCloudEnabled? MPTestFlightCheckpointCloudEnabled: MPTestFlightCheckpointCloudDisabled];
#endif #endif
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO"); inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled]; [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 #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:PearlString(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)]; [TestFlight passCheckpoint:PearlString(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)];
#endif #endif
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error); err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
switch (cause) { switch (cause) {
case UbiquityStoreManagerErrorCauseDeleteStore: case UbiquityStoreManagerErrorCauseDeleteStore:
case UbiquityStoreManagerErrorCauseDeleteLogs: case UbiquityStoreManagerErrorCauseDeleteLogs:
@ -177,7 +179,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
wrn(@"Local store could not be opened, resetting it."); wrn(@"Local store could not be opened, resetting it.");
manager.hardResetEnabled = YES; manager.hardResetEnabled = YES;
[manager hardResetLocalStorage]; [manager hardResetLocalStorage];
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."]; [NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."];
return; return;
} }
@ -196,10 +198,10 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#pragma mark - Import / Export #pragma mark - Import / Export
- (void)loadRFC3339DateFormatter { - (void)loadRFC3339DateFormatter {
if (rfc3339DateFormatter) if (rfc3339DateFormatter)
return; return;
rfc3339DateFormatter = [NSDateFormatter new]; rfc3339DateFormatter = [NSDateFormatter new];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[rfc3339DateFormatter setLocale:enUSPOSIXLocale]; [rfc3339DateFormatter setLocale:enUSPOSIXLocale];
@ -209,36 +211,36 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password - (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation { askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
[self loadRFC3339DateFormatter]; [self loadRFC3339DateFormatter];
static NSRegularExpression *headerPattern, *sitePattern; static NSRegularExpression *headerPattern, *sitePattern;
__autoreleasing NSError *error; __autoreleasing NSError *error;
if (!headerPattern) { if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc] headerPattern = [[NSRegularExpression alloc]
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)" initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:0 error:&error]; options:0 error:&error];
if (error) if (error)
err(@"Error loading the header pattern: %@", error); err(@"Error loading the header pattern: %@", error);
} }
if (!sitePattern) { if (!sitePattern) {
sitePattern = [[NSRegularExpression alloc] sitePattern = [[NSRegularExpression alloc]
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)" initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
options:0 error:&error]; options:0 error:&error];
if (error) if (error)
err(@"Error loading the site pattern: %@", error); err(@"Error loading the site pattern: %@", error);
} }
if (!headerPattern || !sitePattern) if (!headerPattern || !sitePattern)
return MPImportResultInternalError; return MPImportResultInternalError;
NSString *keyIDHex = nil, *userName = nil; NSString *keyIDHex = nil, *userName = nil;
MPUserEntity *user = nil; MPUserEntity *user = nil;
BOOL headerStarted = NO, headerEnded = NO; BOOL headerStarted = NO, headerEnded = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set]; NSMutableSet *elementsToDelete = [NSMutableSet set];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]]; NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
for(NSString *importedSiteLine in importedSiteLines) { for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) { if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header // Comment or header
if (!headerStarted) { if (!headerStarted) {
@ -252,18 +254,19 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
headerEnded = YES; headerEnded = YES;
continue; continue;
} }
// Header // Header
if ([headerPattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) { if ([headerPattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
err(@"Invalid header format in line: %@", importedSiteLine); err(@"Invalid header format in line: %@", importedSiteLine);
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
} }
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject]; NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]]; range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]]; NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([key isEqualToString:@"User Name"]) { if ([key isEqualToString:@"User Name"]) {
userName = value; userName = value;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject]; user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
@ -272,7 +275,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]]) if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]])
return MPImportResultInvalidPassword; return MPImportResultInvalidPassword;
} }
continue; continue;
} }
if (!headerEnded) if (!headerEnded)
@ -281,81 +284,83 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
if (![importedSiteLine length]) if (![importedSiteLine length])
continue; continue;
// Site // Site
if ([sitePattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) { if ([sitePattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
err(@"Invalid site format in line: %@", importedSiteLine); err(@"Invalid site format in line: %@", importedSiteLine);
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
} }
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject]; NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
// Find existing site. // Find existing site.
if (user) { if (user) {
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error]; NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) if (error)
err(@"Couldn't search existing sites: %@", error); err(@"Couldn't search existing sites: %@", error);
if (!existingSites) if (!existingSites)
return MPImportResultInternalError; return MPImportResultInternalError;
[elementsToDelete addObjectsFromArray:existingSites]; [elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]]; [importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
} }
} }
// Ask for confirmation to import these sites. // Ask for confirmation to import these sites.
if (!confirmation([importedSiteElements count], [elementsToDelete count])) if (!confirmation([importedSiteElements count], [elementsToDelete count]))
return MPImportResultCancelled; return MPImportResultCancelled;
// Delete existing sites. // Delete existing sites.
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]); inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
[self.managedObjectContext deleteObject:obj]; [self.managedObjectContext deleteObject:obj];
}]; }];
[self saveContext]; [self saveContext];
// Import new sites. // Import new sites.
if (!user) { if (!user) {
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) inManagedObjectContext:self.managedObjectContext]; user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
user.name = userName; inManagedObjectContext:self.managedObjectContext];
user.name = userName;
user.keyID = [keyIDHex decodeHex]; user.keyID = [keyIDHex decodeHex];
} }
for (NSArray *siteElements in importedSiteElements) { for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]]; NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]];
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue]; NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue]; MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
NSString *name = [siteElements objectAtIndex:3]; NSString *name = [siteElements objectAtIndex:3];
NSString *exportContent = [siteElements objectAtIndex:4]; NSString *exportContent = [siteElements objectAtIndex:4];
// Create new site. // Create new site.
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:self.managedObjectContext]; inManagedObjectContext:self.managedObjectContext];
element.name = name; element.name = name;
element.user = user; element.user = user;
element.type = type; element.type = type;
element.uses = uses; element.uses = uses;
element.lastUsed = lastUsed; element.lastUsed = lastUsed;
if ([exportContent length]) if ([exportContent length])
[element importContent:exportContent]; [element importContent:exportContent];
} }
[self saveContext]; [self saveContext];
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported]; [TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported];
#endif #endif
return MPImportResultSuccess; return MPImportResultSuccess;
} }
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords { - (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
[self loadRFC3339DateFormatter]; [self loadRFC3339DateFormatter];
// Header. // Header.
NSMutableString *export = [NSMutableString new]; NSMutableString *export = [NSMutableString new];
[export appendFormat:@"# Master Password site export\n"]; [export appendFormat:@"# Master Password site export\n"];
@ -377,31 +382,33 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[export appendFormat:@"#\n"]; [export appendFormat:@"#\n"];
[export appendFormat:@"# Last Times Password Site\tSite\n"]; [export appendFormat:@"# Last Times Password Site\tSite\n"];
[export appendFormat:@"# used used type name\tpassword\n"]; [export appendFormat:@"# used used type name\tpassword\n"];
// Sites. // Sites.
for (MPElementEntity *element in self.activeUser.elements) { for (MPElementEntity *element in self.activeUser.elements) {
NSDate *lastUsed = element.lastUsed; NSDate *lastUsed = element.lastUsed;
NSUInteger uses = element.uses; NSUInteger uses = element.uses;
MPElementType type = element.type; MPElementType type = element.type;
NSString *name = element.name; NSString *name = element.name;
NSString *content = nil; NSString *content = nil;
// Determine the content to export. // Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) { if (!(type & MPElementFeatureDevicePrivate)) {
if (showPasswords) if (showPasswords)
content = element.content; content = element.content;
else if (type & MPElementFeatureExportContent) else
content = element.exportContent; if (type & MPElementFeatureExportContent)
content = element.exportContent;
} }
[export appendFormat:@"%@ %8d %8d %20s\t%@\n", [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 #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported]; [TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported];
#endif #endif
return export; return export;
} }

View File

@ -6,32 +6,31 @@
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPConfig.h"
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
@implementation MPConfig @implementation MPConfig
@dynamic rememberLogin, iCloud, iCloudDecided; @dynamic rememberLogin, iCloud, iCloudDecided;
- (id)init { - (id)init {
if(!(self = [super init])) if (!(self = [super init]))
return nil; return nil;
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: [self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)), [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
nil]]; nil]];
self.delegate = [MPAppDelegate get]; self.delegate = [MPAppDelegate get];
return self; return self;
} }
+ (MPConfig *)get { + (MPConfig *)get {
return (MPConfig *)[super get]; return (MPConfig *)[super get];
} }

View File

@ -14,10 +14,10 @@
@interface MPElementEntity : NSManagedObject @interface MPElementEntity : NSManagedObject
@property (nonatomic, retain) id content; @property (nonatomic, retain) id content;
@property (nonatomic, retain) NSDate * lastUsed; @property (nonatomic, retain) NSDate *lastUsed;
@property (nonatomic, retain) NSString * name; @property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSNumber * type_; @property (nonatomic, retain) NSNumber *type_;
@property (nonatomic, retain) NSNumber * uses_; @property (nonatomic, retain) NSNumber *uses_;
@property (nonatomic, retain) MPUserEntity *user; @property (nonatomic, retain) MPUserEntity *user;
@end @end

View File

@ -7,7 +7,6 @@
// //
#import "MPElementEntity.h" #import "MPElementEntity.h"
#import "MPUserEntity.h"
@implementation MPElementEntity @implementation MPElementEntity

View File

@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity @interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, retain) NSNumber * counter_; @property (nonatomic, retain) NSNumber *counter_;
@end @end

View File

@ -17,7 +17,7 @@
@interface MPElementEntity (MP) @interface MPElementEntity (MP)
@property (assign) MPElementType type; @property (assign) MPElementType type;
@property (assign) NSUInteger uses; @property (assign) NSUInteger uses;
- (NSUInteger)use; - (NSUInteger)use;
- (NSString *)exportContent; - (NSString *)exportContent;
@ -34,6 +34,6 @@
@interface MPUserEntity (MP) @interface MPUserEntity (MP)
@property (assign) NSUInteger avatar; @property (assign) NSUInteger avatar;
@property (assign) BOOL saveKey; @property (assign) BOOL saveKey;
@end @end

View File

@ -13,12 +13,12 @@
@implementation MPElementEntity (MP) @implementation MPElementEntity (MP)
- (MPElementType)type { - (MPElementType)type {
return (MPElementType)[self.type_ unsignedIntegerValue]; return (MPElementType)[self.type_ unsignedIntegerValue];
} }
- (void)setType:(MPElementType)type { - (void)setType:(MPElementType)type {
self.type_ = PearlUnsignedInteger(type); self.type_ = PearlUnsignedInteger(type);
} }
@ -34,32 +34,32 @@
- (NSUInteger)use { - (NSUInteger)use {
self.lastUsed = [NSDate date]; self.lastUsed = [NSDate date];
return ++self.uses; return ++self.uses;
} }
- (id)content { - (id)content {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil]; @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
} }
- (NSString *)exportContent { - (NSString *)exportContent {
return nil; return nil;
} }
- (void)importContent:(NSString *)content { - (void)importContent:(NSString *)content {
} }
- (NSString *)description { - (NSString *)description {
return PearlString(@"%@:%@", [self class], [self name]); return PearlString(@"%@:%@", [self class], [self name]);
} }
- (NSString *)debugDescription { - (NSString *)debugDescription {
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}", return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.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); err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
return nil; return nil;
} }
if (![self.name length]) if (![self.name length])
return nil; return nil;
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter); return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter);
} }
@ -96,55 +96,55 @@
@implementation MPElementStoredEntity (MP) @implementation MPElementStoredEntity (MP)
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name { + (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"DevicePrivate", (__bridge id)kSecAttrService, @"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount, name, (__bridge id)kSecAttrAccount,
nil] nil]
matches:nil]; matches:nil];
} }
- (id)content { - (id)content {
assert(self.type & MPElementTypeClassStored); assert(self.type & MPElementTypeClassStored);
NSData *encryptedContent; NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate) if (self.type & MPElementFeatureDevicePrivate)
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]]; encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else else
encryptedContent = self.contentObject; encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize] NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES]; padding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding]; return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
} }
- (void)setContent:(id)content { - (void)setContent:(id)content {
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize] NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES]; padding:YES];
if (self.type & MPElementFeatureDevicePrivate) { if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name] [PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData, encryptedContent, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif #endif
nil]]; nil]];
self.contentObject = nil; self.contentObject = nil;
} else } else
self.contentObject = encryptedContent; self.contentObject = encryptedContent;
} }
- (NSString *)exportContent { - (NSString *)exportContent {
return [self.contentObject encodeBase64]; return [self.contentObject encodeBase64];
} }
- (void)importContent:(NSString *)content { - (void)importContent:(NSString *)content {
self.contentObject = [content decodeBase64]; self.contentObject = [content decodeBase64];
} }

View File

@ -18,28 +18,28 @@ typedef enum {
typedef enum { typedef enum {
/** Generate the password. */ /** Generate the password. */
MPElementTypeClassGenerated = 1 << 4, MPElementTypeClassGenerated = 1 << 4,
/** Store the password. */ /** Store the password. */
MPElementTypeClassStored = 1 << 5, MPElementTypeClassStored = 1 << 5,
} MPElementTypeClass; } MPElementTypeClass;
typedef enum { typedef enum {
/** Export the key-protected content data. */ /** Export the key-protected content data. */
MPElementFeatureExportContent = 1 << 10, MPElementFeatureExportContent = 1 << 10,
/** Never export content. */ /** Never export content. */
MPElementFeatureDevicePrivate = 1 << 11, MPElementFeatureDevicePrivate = 1 << 11,
} MPElementFeature; } MPElementFeature;
typedef enum { typedef enum {
MPElementTypeGeneratedSecure = 0x0 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedSecure = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent, MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate, MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
} MPElementType; } MPElementType;
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction" #define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
@ -77,9 +77,9 @@ typedef enum {
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten" #define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationElementUsed @"MPNotificationElementUsed" #define MPNotificationElementUsed @"MPNotificationElementUsed"
NSData *keyForPassword(NSString *password, NSString *username); NSData *keyForPassword(NSString *password, NSString *username);
NSData *keyIDForPassword(NSString *password, NSString *username); NSData *keyIDForPassword(NSString *password, NSString *username);
NSData *keyIDForKey(NSData *key); NSData *keyIDForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type); NSString *NSStringFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type); NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type); Class ClassFromMPElementType(MPElementType type);

View File

@ -7,7 +7,6 @@
// //
#import "MPTypes.h" #import "MPTypes.h"
#import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h" #import "MPElementStoredEntity.h"
@ -21,101 +20,106 @@ NSData *keyForPassword(NSString *password, NSString *username) {
uint32_t nusernameLength = htonl(username.length); uint32_t nusernameLength = htonl(username.length);
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding] NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
usingSalt:[NSData dataByConcatenatingDatas: usingSalt:[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nusernameLength length:sizeof(nusernameLength)], [NSData dataWithBytes:&nusernameLength
[username dataUsingEncoding:NSUTF8StringEncoding], length:sizeof(nusernameLength)],
nil] N:MP_N r:MP_r p:MP_p]; [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]); trc(@"User: %@, password: %@ derives to key ID: %@", username, password, [keyIDForKey(key) encodeHex]);
return key; return key;
} }
NSData *keyIDForPassword(NSString *password, NSString *username) { NSData *keyIDForPassword(NSString *password, NSString *username) {
return keyIDForKey(keyForPassword(password, username)); return keyIDForKey(keyForPassword(password, username));
} }
NSData *keyIDForKey(NSData *key) { NSData *keyIDForKey(NSData *key) {
return [key hashWith:MP_hash]; return [key hashWith:MP_hash];
} }
NSString *NSStringFromMPElementType(MPElementType type) { NSString *NSStringFromMPElementType(MPElementType type) {
if (!type) if (!type)
return nil; return nil;
switch (type) { switch (type) {
case MPElementTypeGeneratedSecure: case MPElementTypeGeneratedSecure:
return @"Secure Password"; return @"Secure Password";
case MPElementTypeGeneratedLong: case MPElementTypeGeneratedLong:
return @"Long Password"; return @"Long Password";
case MPElementTypeGeneratedMedium: case MPElementTypeGeneratedMedium:
return @"Medium Password"; return @"Medium Password";
case MPElementTypeGeneratedShort: case MPElementTypeGeneratedShort:
return @"Short Password"; return @"Short Password";
case MPElementTypeGeneratedBasic: case MPElementTypeGeneratedBasic:
return @"Basic Password"; return @"Basic Password";
case MPElementTypeGeneratedPIN: case MPElementTypeGeneratedPIN:
return @"PIN"; return @"PIN";
case MPElementTypeStoredPersonal: case MPElementTypeStoredPersonal:
return @"Personal Password"; return @"Personal Password";
case MPElementTypeStoredDevicePrivate: case MPElementTypeStoredDevicePrivate:
return @"Device Private Password"; return @"Device Private Password";
default: default:
Throw(@"Type not supported: %d", type); Throw(@"Type not supported: %d", type);
} }
} }
Class ClassFromMPElementType(MPElementType type) { Class ClassFromMPElementType(MPElementType type) {
if (!type) if (!type)
return nil; return nil;
switch (type) { switch (type) {
case MPElementTypeGeneratedSecure: case MPElementTypeGeneratedSecure:
return [MPElementGeneratedEntity class]; return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedLong: case MPElementTypeGeneratedLong:
return [MPElementGeneratedEntity class]; return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedMedium: case MPElementTypeGeneratedMedium:
return [MPElementGeneratedEntity class]; return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedShort: case MPElementTypeGeneratedShort:
return [MPElementGeneratedEntity class]; return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedBasic: case MPElementTypeGeneratedBasic:
return [MPElementGeneratedEntity class]; return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedPIN: case MPElementTypeGeneratedPIN:
return [MPElementGeneratedEntity class]; return [MPElementGeneratedEntity class];
case MPElementTypeStoredPersonal: case MPElementTypeStoredPersonal:
return [MPElementStoredEntity class]; return [MPElementStoredEntity class];
case MPElementTypeStoredDevicePrivate: case MPElementTypeStoredDevicePrivate:
return [MPElementStoredEntity class]; return [MPElementStoredEntity class];
default: default:
Throw(@"Type not supported: %d", type); Throw(@"Type not supported: %d", type);
} }
} }
NSString *ClassNameFromMPElementType(MPElementType type) { NSString *ClassNameFromMPElementType(MPElementType type) {
return NSStringFromClass(ClassFromMPElementType(type)); return NSStringFromClass(ClassFromMPElementType(type));
} }
static NSDictionary *MPTypes_ciphers = nil; static NSDictionary *MPTypes_ciphers = nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) { NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
if (!(type & MPElementTypeClassGenerated)) { if (!(type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name); err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
return nil; return nil;
@ -129,46 +133,47 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui
return nil; return nil;
} }
if (!counter) if (!counter)
// Counter unset, go into OTP mode. // 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. // 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; counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
if (MPTypes_ciphers == nil) if (MPTypes_ciphers == nil)
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers" MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
withExtension:@"plist"]]; withExtension:@"plist"]];
// Determine the seed whose bytes will be used for calculating a password // 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); 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); uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
NSData *seed = [[NSData dataByConcatenatingDatas: NSData *seed = [[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)], [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
[name dataUsingEncoding:NSUTF8StringEncoding], [name dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)], [NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
nil] nil]
hmacWith:PearlHashSHA256 key:key]; hmacWith:PearlHashSHA256 key:key];
trc(@"seed is: %@", seed); trc(@"seed is: %@", seed);
const char *seedBytes = seed.bytes; const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte. // Determine the cipher from the first seed byte.
assert([seed length]); assert([seed length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)] NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
valueForKey:NSStringFromMPElementType(type)]; valueForKey:NSStringFromMPElementType(type)];
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]]; NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher); trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
// Encode the content, character by character, using subsequent seed bytes and the cipher. // Encode the content, character by character, using subsequent seed bytes and the cipher.
assert([seed length] >= [cipher length] + 1); assert([seed length] >= [cipher length] + 1);
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]]; NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
for (NSUInteger c = 0; c < [cipher length]; ++c) { for (NSUInteger c = 0; c < [cipher length]; ++c) {
uint16_t keyByte = htons(seedBytes[c + 1]); 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 *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); trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
[content appendString:character]; [content appendString:character];
} }
return content; return content;
} }

View File

@ -13,12 +13,12 @@
@interface MPUserEntity : NSManagedObject @interface MPUserEntity : NSManagedObject
@property (nonatomic, retain) NSNumber * avatar_; @property (nonatomic, retain) NSNumber *avatar_;
@property (nonatomic, retain) NSData * keyID; @property (nonatomic, retain) NSData *keyID;
@property (nonatomic, retain) NSDate * lastUsed; @property (nonatomic, retain) NSDate *lastUsed;
@property (nonatomic, retain) NSString * name; @property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSNumber * saveKey_; @property (nonatomic, retain) NSNumber *saveKey_;
@property (nonatomic, retain) NSSet *elements; @property (nonatomic, retain) NSSet *elements;
@end @end
@interface MPUserEntity (CoreDataGeneratedAccessors) @interface MPUserEntity (CoreDataGeneratedAccessors)

View File

@ -7,7 +7,6 @@
// //
#import "MPUserEntity.h" #import "MPUserEntity.h"
#import "MPElementEntity.h"
@implementation MPUserEntity @implementation MPUserEntity

View File

@ -10,16 +10,16 @@
#import "MPAppDelegate_Shared.h" #import "MPAppDelegate_Shared.h"
#import "MPPasswordWindowController.h" #import "MPPasswordWindowController.h"
@interface MPAppDelegate : MPAppDelegate_Shared <NSApplicationDelegate> @interface MPAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
@property (strong) NSStatusItem *statusItem; @property (strong) NSStatusItem *statusItem;
@property (strong) MPPasswordWindowController *passwordWindow; @property (strong) MPPasswordWindowController *passwordWindow;
@property (weak) IBOutlet NSMenuItem *lockItem; @property (weak) IBOutlet NSMenuItem *lockItem;
@property (weak) IBOutlet NSMenuItem *showItem; @property (weak) IBOutlet NSMenuItem *showItem;
@property (strong) IBOutlet NSMenu *statusMenu; @property (strong) IBOutlet NSMenu *statusMenu;
@property (weak) IBOutlet NSMenuItem *useICloudItem; @property (weak) IBOutlet NSMenuItem *useICloudItem;
@property (weak) IBOutlet NSMenuItem *rememberPasswordItem; @property (weak) IBOutlet NSMenuItem *rememberPasswordItem;
@property (weak) IBOutlet NSMenuItem *savePasswordItem; @property (weak) IBOutlet NSMenuItem *savePasswordItem;
+ (MPAppDelegate *)get; + (MPAppDelegate *)get;

View File

@ -28,49 +28,49 @@
@synthesize keyID; @synthesize keyID;
#pragma GCC diagnostic ignored "-Wfour-char-constants" #pragma GCC diagnostic ignored "-Wfour-char-constants"
static EventHotKeyID MPShowHotKey = { .signature = 'show', .id = 1 }; static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1};
+ (void)initialize { + (void)initialize {
[MPConfig get]; [MPConfig get];
#ifdef DEBUG #ifdef DEBUG
[PearlLogger get].autoprintLevel = PearlLogLevelTrace; [PearlLogger get].autoprintLevel = PearlLogLevelTrace;
#endif #endif
} }
+ (MPAppDelegate *)get { + (MPAppDelegate *)get {
return (MPAppDelegate *)[super 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. // Extract the hotkey ID.
EventHotKeyID hotKeyID; EventHotKeyID hotKeyID;
GetEventParameter(theEvent,kEventParamDirectObject,typeEventHotKeyID, GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID,
NULL,sizeof(hotKeyID),NULL,&hotKeyID); NULL, sizeof(hotKeyID), NULL, &hotKeyID);
// Check which hotkey this was. // Check which hotkey this was.
if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) { if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) {
[((__bridge MPAppDelegate *)userData) activate:nil]; [((__bridge MPAppDelegate *)userData) activate:nil];
return noErr; return noErr;
} }
return eventNotHandledErr; return eventNotHandledErr;
} }
- (void)showMenu { - (void)showMenu {
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState; self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState; self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
self.showItem.enabled = ![self.passwordWindow.window isVisible]; self.showItem.enabled = ![self.passwordWindow.window isVisible];
[self.statusItem popUpStatusItemMenu:self.statusMenu]; [self.statusItem popUpStatusItemMenu:self.statusMenu];
} }
- (IBAction)activate:(id)sender { - (IBAction)activate:(id)sender {
if ([[NSApplication sharedApplication] isActive]) if ([[NSApplication sharedApplication] isActive])
[self applicationDidBecomeActive:nil]; [self applicationDidBecomeActive:nil];
else else
@ -78,13 +78,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
} }
- (IBAction)togglePreference:(NSMenuItem *)sender { - (IBAction)togglePreference:(NSMenuItem *)sender {
if (sender == useICloudItem) if (sender == useICloudItem)
[self.storeManager useiCloudStore:sender.state == NSOffState alertUser:YES]; [self.storeManager useiCloudStore:sender.state == NSOffState alertUser:YES];
if (sender == rememberPasswordItem) if (sender == rememberPasswordItem)
[MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]]; [MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]];
if (sender == savePasswordItem) 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 { - (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
@ -92,11 +92,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
if (configKey == @selector(rememberKey)) if (configKey == @selector(rememberKey))
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState; self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
if (configKey == @selector(saveKey)) 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 { - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"key"]) { if ([keyPath isEqualToString:@"key"]) {
if (self.key) if (self.key)
[self.lockItem setEnabled:YES]; [self.lockItem setEnabled:YES];
@ -108,49 +108,50 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
} }
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window { - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
return [[self managedObjectContext] undoManager]; return [[self managedObjectContext] undoManager];
} }
#pragma mark - NSApplicationDelegate #pragma mark - NSApplicationDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Setup delegates and listeners. // Setup delegates and listeners.
[MPConfig get].delegate = self; [MPConfig get].delegate = self;
[self addObserver:self forKeyPath:@"key" options:0 context:nil]; [self addObserver:self forKeyPath:@"key" options:0 context:nil];
// Initially, use iCloud. // Initially, use iCloud.
if ([[MPConfig get].firstRun boolValue]) if ([[MPConfig get].firstRun boolValue])
[[self storeManager] useiCloudStore:YES alertUser:YES]; [[self storeManager] useiCloudStore:YES alertUser:YES];
// Status item. // Status item.
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
self.statusItem.title = @"•••"; self.statusItem.title = @"•••";
self.statusItem.highlightMode = YES; self.statusItem.highlightMode = YES;
self.statusItem.target = self; self.statusItem.target = self;
self.statusItem.action = @selector(showMenu); self.statusItem.action = @selector(showMenu);
// Global hotkey. // Global hotkey.
EventHotKeyRef hotKeyRef; EventHotKeyRef hotKeyRef;
EventTypeSpec hotKeyEvents[1] = { { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed } }; EventTypeSpec hotKeyEvents[1] = {{.eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed}};
OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents), hotKeyEvents, OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents),
(__bridge void *)self, NULL); hotKeyEvents,
if(status != noErr) (__bridge void *)self, NULL);
err(@"Error installing application event handler: %d", status); if (status != noErr)
status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef); err(@"Error installing application event handler: %d", status);
if(status != noErr) status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef);
err(@"Error registering hotkey: %d", status); if (status != noErr)
err(@"Error registering hotkey: %d", status);
} }
- (void)applicationWillBecomeActive:(NSNotification *)notification { - (void)applicationWillBecomeActive:(NSNotification *)notification {
if (!self.passwordWindow) if (!self.passwordWindow)
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"]; self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
} }
- (void)applicationDidBecomeActive:(NSNotification *)notification { - (void)applicationDidBecomeActive:(NSNotification *)notification {
static BOOL firstTime = YES; static BOOL firstTime = YES;
if (firstTime) if (firstTime)
firstTime = NO; firstTime = NO;
@ -159,62 +160,61 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
} }
- (void)applicationWillResignActive:(NSNotification *)notification { - (void)applicationWillResignActive:(NSNotification *)notification {
if (![[MPConfig get].rememberKey boolValue]) if (![[MPConfig get].rememberKey boolValue])
self.key = nil; self.key = nil;
} }
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
{
// Save changes in the application's managed object context before the application terminates. // Save changes in the application's managed object context before the application terminates.
if (![self managedObjectContext]) { if (![self managedObjectContext]) {
return NSTerminateNow; return NSTerminateNow;
} }
if (![[self managedObjectContext] commitEditing]) { if (![[self managedObjectContext] commitEditing]) {
NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd)); NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd));
return NSTerminateCancel; return NSTerminateCancel;
} }
if (![[self managedObjectContext] hasChanges]) { if (![[self managedObjectContext] hasChanges]) {
return NSTerminateNow; return NSTerminateNow;
} }
NSError *error = nil; NSError *error = nil;
if (![[self managedObjectContext] save:&error]) { if (![[self managedObjectContext] save:&error]) {
// Customize this code block to include application-specific recovery steps. // Customize this code block to include application-specific recovery steps.
BOOL result = [sender presentError:error]; BOOL result = [sender presentError:error];
if (result) { if (result) {
return NSTerminateCancel; return NSTerminateCancel;
} }
NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message"); 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 *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 *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title"); NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title");
NSAlert *alert = [[NSAlert alloc] init]; NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:question]; [alert setMessageText:question];
[alert setInformativeText:info]; [alert setInformativeText:info];
[alert addButtonWithTitle:quitButton]; [alert addButtonWithTitle:quitButton];
[alert addButtonWithTitle:cancelButton]; [alert addButtonWithTitle:cancelButton];
NSInteger answer = [alert runModal]; NSInteger answer = [alert runModal];
if (answer == NSAlertAlternateReturn) { if (answer == NSAlertAlternateReturn) {
return NSTerminateCancel; return NSTerminateCancel;
} }
} }
return NSTerminateNow; return NSTerminateNow;
} }
#pragma mark - UbiquityStoreManagerDelegate #pragma mark - UbiquityStoreManagerDelegate
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState; self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState;
self.useICloudItem.enabled = !iCloudEnabled; self.useICloudItem.enabled = !iCloudEnabled;
} }

View File

@ -8,8 +8,8 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@interface MPPasswordWindowController : NSWindowController <NSTextFieldDelegate> { @interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate> {
NSString *_content; NSString *_content;
} }

View File

@ -27,10 +27,10 @@
@synthesize tipField; @synthesize tipField;
- (void)windowDidLoad { - (void)windowDidLoad {
[self setContent:@""]; [self setContent:@""];
[self.tipField setStringValue:@""]; [self.tipField setStringValue:@""];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[self unlock]; [self unlock];
@ -45,32 +45,32 @@
NSString *newSiteName = [self.siteField stringValue]; NSString *newSiteName = [self.siteField stringValue];
BOOL shouldComplete = [self.oldSiteName length] < [newSiteName length]; BOOL shouldComplete = [self.oldSiteName length] < [newSiteName length];
self.oldSiteName = newSiteName; self.oldSiteName = newSiteName;
if ([self trySite]) if ([self trySite])
shouldComplete = NO; shouldComplete = NO;
if (shouldComplete) if (shouldComplete)
[[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil]; [[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil];
}]; }];
[super windowDidLoad]; [super windowDidLoad];
} }
- (void)unlock { - (void)unlock {
if (![MPAppDelegate get].key) if (![MPAppDelegate get].key)
// Try and load the key from the keychain. // Try and load the key from the keychain.
[[MPAppDelegate get] loadStoredKey]; [[MPAppDelegate get] loadStoredKey];
if (![MPAppDelegate get].key) 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(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if ([MPAppDelegate get].key) if ([MPAppDelegate get].key)
return; return;
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked." NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit" defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
informativeTextWithFormat:@"Your master password is required to unlock the application."]; informativeTextWithFormat:@"Your master password is required to unlock the application."];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)]; NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)];
[alert setAccessoryView:passwordField]; [alert setAccessoryView:passwordField];
[alert layout]; [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) { switch (returnCode) {
case NSAlertAlternateReturn: case NSAlertAlternateReturn:
// "Change" button. // "Change" button.
if ([[NSAlert alertWithMessageText:@"Changing Master Password" if ([[NSAlert alertWithMessageText:@"Changing Master Password"
defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil
informativeTextWithFormat: informativeTextWithFormat:
@"This will allow you to log in with a different master password.\n\n" @"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" @"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" @"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" @"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."] runModal] == 1) @"Your current sites and passwords will then become available again."] runModal]
== 1)
[[MPAppDelegate get] forgetKey]; [[MPAppDelegate get] forgetKey];
break; break;
case NSAlertOtherReturn: case NSAlertOtherReturn:
// "Quit" button. // "Quit" button.
[[NSApplication sharedApplication] terminate:self]; [[NSApplication sharedApplication] terminate:self];
return; return;
case NSAlertDefaultReturn: case NSAlertDefaultReturn:
// "Unlock" button. // "Unlock" button.
[[MPAppDelegate get] tryMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]]; [[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]; NSString *query = [[control stringValue] substringWithRange:charRange];
if (![query length] || ![MPAppDelegate get].keyID) if (![query length] || ![MPAppDelegate get].keyID)
return nil; return nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@", fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
query, query, [MPAppDelegate get].activeUser]; query, query, [MPAppDelegate get].activeUser];
NSError *error = nil; NSError *error = nil;
self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error]; self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (error) if (error)
err(@"Couldn't fetch elements: %@", error); err(@"Couldn't fetch elements: %@", error);
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1]; NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
if (self.siteResults) if (self.siteResults)
for (MPElementEntity *element in self.siteResults) for (MPElementEntity *element in self.siteResults)
[mutableResults addObject:element.name]; [mutableResults addObject:element.name];
@ -132,7 +134,7 @@
} }
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector { - (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
if (commandSelector == @selector(cancel:)) { if (commandSelector == @selector(cancel:)) {
[self.window close]; [self.window close];
return YES; return YES;
@ -149,68 +151,68 @@
[self.tipField.animator setAlphaValue:0]; [self.tipField.animator setAlphaValue:0];
[NSAnimationContext endGrouping]; [NSAnimationContext endGrouping];
}); });
[[self findElement] use]; [[self findElement] use];
return YES; return YES;
} else } else
wrn(@"Couldn't copy password to pasteboard."); wrn(@"Couldn't copy password to pasteboard.");
} }
return NO; return NO;
} }
- (void)controlTextDidEndEditing:(NSNotification *)obj { - (void)controlTextDidEndEditing:(NSNotification *)obj {
if (obj.object == self.siteField) if (obj.object == self.siteField)
[self trySite]; [self trySite];
} }
- (NSString *)content { - (NSString *)content {
return _content; return _content;
} }
- (void)setContent:(NSString *)content { - (void)setContent:(NSString *)content {
_content = content; _content = content;
NSShadow *shadow = [NSShadow new]; NSShadow *shadow = [NSShadow new];
shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f]; shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f];
shadow.shadowOffset = NSMakeSize(1.0f, -1.0f); shadow.shadowOffset = NSMakeSize(1.0f, -1.0f);
shadow.shadowBlurRadius = 1.2f; shadow.shadowBlurRadius = 1.2f;
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new]; NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
paragraph.alignment = NSCenterTextAlignment; paragraph.alignment = NSCenterTextAlignment;
[self.contentField setAttributedStringValue: [self.contentField setAttributedStringValue:
[[NSAttributedString alloc] initWithString:_content [[NSAttributedString alloc] initWithString:_content
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys: attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
shadow, NSShadowAttributeName, shadow, NSShadowAttributeName,
paragraph, NSParagraphStyleAttributeName, paragraph, NSParagraphStyleAttributeName,
nil]]]; nil]]];
} }
- (BOOL)trySite { - (BOOL)trySite {
MPElementEntity *result = [self findElement]; MPElementEntity *result = [self findElement];
if (!result) { if (!result) {
[self setContent:@""]; [self setContent:@""];
[self.tipField setStringValue:@""]; [self.tipField setStringValue:@""];
return NO; return NO;
} }
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [result.content description]; NSString *description = [result.content description];
if (!description) if (!description)
description = @""; description = @"";
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self setContent:description]; [self setContent:description];
[self.tipField setStringValue:@"Hit enter to copy the password."]; [self.tipField setStringValue:@"Hit enter to copy the password."];
self.tipField.alphaValue = 1; self.tipField.alphaValue = 1;
}); });
}); });
// For when the app should be able to create new sites. // For when the app should be able to create new sites.
/* /*
else else
@ -231,16 +233,16 @@
}); });
}]; }];
*/ */
return YES; return YES;
} }
- (MPElementEntity *)findElement { - (MPElementEntity *)findElement {
for (MPElementEntity *element in self.siteResults) for (MPElementEntity *element in self.siteResults)
if ([element.name isEqualToString:[self.siteField stringValue]]) if ([element.name isEqualToString:[self.siteField stringValue]])
return element; return element;
return nil; return nil;
} }

View File

@ -3,7 +3,9 @@
// //
#ifdef __OBJC__ #ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#import <Cocoa/Cocoa.h>
#endif #endif
#import "Pearl-Prefix.pch" #import "Pearl-Prefix.pch"

View File

@ -8,7 +8,7 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{
return NSApplicationMain(argc, (const char **)argv); return NSApplicationMain(argc, (const char **)argv);
} }

View File

@ -10,7 +10,7 @@
#import <MessageUI/MessageUI.h> #import <MessageUI/MessageUI.h>
#import "MPAppDelegate_Shared.h" #import "MPAppDelegate_Shared.h"
@interface MPAppDelegate : MPAppDelegate_Shared <MFMailComposeViewControllerDelegate> @interface MPAppDelegate : MPAppDelegate_Shared<MFMailComposeViewControllerDelegate>
+ (MPAppDelegate *)get; + (MPAppDelegate *)get;

View File

@ -12,8 +12,6 @@
#import "IASKSettingsReader.h" #import "IASKSettingsReader.h"
#import "LocalyticsSession.h" #import "LocalyticsSession.h"
#import "TestFlight.h"
#import <Crashlytics/Crashlytics.h>
#import "ATConnect.h" #import "ATConnect.h"
@interface MPAppDelegate () @interface MPAppDelegate ()
@ -36,9 +34,9 @@
@implementation MPAppDelegate @implementation MPAppDelegate
+ (void)initialize { + (void)initialize {
[MPiOSConfig get]; [MPiOSConfig get];
#ifdef DEBUG #ifdef DEBUG
[PearlLogger get].autoprintLevel = PearlLogLevelDebug; [PearlLogger get].autoprintLevel = PearlLogLevelDebug;
//[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")]; //[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
@ -46,7 +44,7 @@
} }
+ (MPAppDelegate *)get { + (MPAppDelegate *)get {
return (MPAppDelegate *)[super get]; return (MPAppDelegate *)[super get];
} }
@ -57,54 +55,55 @@
} }
- (void)showGuide { - (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self]; [self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide]; [TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
} }
- (void)export { - (void)export {
[PearlAlert showNotice: [PearlAlert showNotice:
@"This will export all your site names.\n\n" @"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" @"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." @"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) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message: [PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
@"Would you like to make all your passwords visible in the export?\n\n" @"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, " @"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" @"making the result safe from falling in the wrong hands.\n\n"
@"If all your passwords are shown and somebody else finds the export, " @"If all your passwords are shown and somebody else finds the export, "
@"they could gain access to all your sites!" @"they could gain access to all your sites!"
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { viewStyle:UIAlertViewStyleDefault initAlert:nil
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0) tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
// Safe Export if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0)
[self exportShowPasswords:NO]; // Safe Export
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1) [self exportShowPasswords:NO];
// Safe Export if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
[self exportShowPasswords:YES]; // Safe Export
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil]; [self exportShowPasswords:YES];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
} otherTitles:nil]; } otherTitles:nil];
} }
- (void)exportShowPasswords:(BOOL)showPasswords { - (void)exportShowPasswords:(BOOL)showPasswords {
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords]; NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
NSString *message; NSString *message;
if (showPasswords) if (showPasswords)
message = @"Export of my Master Password sites with passwords visible.\n\nREMINDER: Make sure nobody else sees this file!\n"; message = @"Export of my Master Password sites with passwords visible.\n\nREMINDER: Make sure nobody else sees this file!\n";
else else
message = @"Backup of my Master Password sites.\n"; message = @"Backup of my Master Password sites.\n";
NSDateFormatter *exportDateFormatter = [NSDateFormatter new]; NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"'Master Password sites ('yyyy'-'MM'-'DD').mpsites'"]; [exportDateFormatter setDateFormat:@"'Master Password sites ('yyyy'-'MM'-'DD').mpsites'"];
MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init]; MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init];
[composer setMailComposeDelegate:self]; [composer setMailComposeDelegate:self];
[composer setSubject:@"Master Password site export"]; [composer setSubject:@"Master Password site export"];
[composer setMessageBody:message isHTML:NO]; [composer setMessageBody:message isHTML:NO];
[composer addAttachmentData:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain" [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]; [self.window.rootViewController presentModalViewController:composer animated:YES];
} }
@ -112,29 +111,29 @@
[PearlAlert showAlertWithTitle:@"Changing Master Password" [PearlAlert showAlertWithTitle:@"Changing Master Password"
message: message:
@"This will allow you to log in with a different master password.\n\n" @"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" @"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" @"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" @"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again." @"Your current sites and passwords will then become available again."
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
self.activeUser.keyID = nil; self.activeUser.keyID = nil;
[self signOut]; [self signOut];
[TestFlight passCheckpoint:MPTestFlightCheckpointChangeMP]; [TestFlight passCheckpoint:MPTestFlightCheckpointChangeMP];
} }
cancelTitle:[PearlStrings get].commonButtonAbort cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil]; otherTitles:[PearlStrings get].commonButtonContinue, nil];
} }
#pragma mark - PearlConfigDelegate #pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value { - (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
[self checkConfig]; [self checkConfig];
} }
@ -142,10 +141,10 @@
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"]; [[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"]; [[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
#ifndef DEBUG #ifndef DEBUG
@try { @try {
@ -205,12 +204,12 @@
} }
#endif #endif
}); });
@try { @try {
NSString *apptentiveAPIKey = [self apptentiveAPIKey]; NSString *apptentiveAPIKey = [self apptentiveAPIKey];
if ([apptentiveAPIKey length]) { if ([apptentiveAPIKey length]) {
dbg(@"Initializing Apptentive"); dbg(@"Initializing Apptentive");
ATConnect *connection = [ATConnect sharedConnection]; ATConnect *connection = [ATConnect sharedConnection];
[connection setApiKey:apptentiveAPIKey]; [connection setApiKey:apptentiveAPIKey];
[connection setShouldTakeScreenshot:NO]; [connection setShouldTakeScreenshot:NO];
@ -220,58 +219,58 @@
@catch (NSException *exception) { @catch (NSException *exception) {
err(@"Apptentive: %@", 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:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone]; [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone];
[[UINavigationBar appearance] setTitleTextAttributes: [[UINavigationBar appearance] setTitleTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys: [NSDictionary dictionaryWithObjectsAndKeys:
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor, [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, [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset, [NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont, [UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont,
nil]]; nil]];
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)]; 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)]; UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
[[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; [[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; [[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setTitleTextAttributes: [[UIBarButtonItem appearance] setTitleTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys: [NSDictionary dictionaryWithObjectsAndKeys:
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor, [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, [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset, [NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont, [UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
nil] nil]
forState:UIControlStateNormal]; forState:UIControlStateNormal];
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)]; UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
[[UISearchBar appearance] setBackgroundImage:toolBarImage]; [[UISearchBar appearance] setBackgroundImage:toolBarImage];
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault]; [[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
/* /*
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)]; 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 *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"]; UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal]; [[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal]; [[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal]; [[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)]; 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 *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"]; UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"]; UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"]; UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault]; [[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal 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:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/ [[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
@ -281,7 +280,7 @@
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[self checkConfig]; [self checkConfig];
}]; }];
#ifdef ADHOC #ifdef ADHOC
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message: [PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
@"Thank you for taking the time to test Master Password.\n\n" @"Thank you for taking the time to test Master Password.\n\n"
@ -293,56 +292,59 @@
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil]; cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#endif #endif
[[UIApplication sharedApplication] setStatusBarHidden:NO [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
withAnimation:UIStatusBarAnimationSlide];
return [super application:application didFinishLaunchingWithOptions:launchOptions]; return [super application:application didFinishLaunchingWithOptions:launchOptions];
} }
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
__autoreleasing NSError *error; __autoreleasing NSError *error;
__autoreleasing NSURLResponse *response; __autoreleasing NSURLResponse *response;
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error]; returningResponse:&response error:&error];
if (error) if (error)
err(@"While reading imported sites from %@: %@", url, error); err(@"While reading imported sites from %@: %@", url, error);
if (!importedSitesData) if (!importedSitesData)
return NO; return NO;
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
[PearlAlert showAlertWithTitle:@"Import Password" message: [PearlAlert showAlertWithTitle:@"Import Password" message:
@"Enter the master password for this export:" @"Enter the master password for this export:"
viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock: viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) { ^(UIAlertView *alert, NSInteger buttonIndex) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) { askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
__block BOOL confirmation = NO; __block BOOL confirmation = NO;
dispatch_group_t confirmationGroup = dispatch_group_create(); dispatch_group_t confirmationGroup = dispatch_group_create();
dispatch_group_enter(confirmationGroup); dispatch_group_enter(confirmationGroup);
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import Sites?" [PearlAlert showAlertWithTitle:@"Import Sites?"
message:PearlString(@"Import %d sites, overwriting %d existing sites?", importCount, deleteCount) message:PearlString(
viewStyle:UIAlertViewStyleDefault @"Import %d sites, overwriting %d existing sites?",
initAlert:nil importCount, deleteCount)
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { viewStyle:UIAlertViewStyleDefault
if (buttonIndex_ != [alert_ cancelButtonIndex]) initAlert:nil
confirmation = YES; tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_
dispatch_group_leave(confirmationGroup); != [alert_ cancelButtonIndex])
} confirmation = YES;
cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:@"Import", nil]; dispatch_group_leave(confirmationGroup);
}); }
dispatch_group_wait(confirmationGroup, DISPATCH_TIME_FOREVER); cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:@"Import", nil];
return confirmation; });
}]; dispatch_group_wait(
confirmationGroup, DISPATCH_TIME_FOREVER);
return confirmation;
}];
switch (result) { switch (result) {
case MPImportResultSuccess: case MPImportResultSuccess:
case MPImportResultCancelled: case MPImportResultCancelled:
@ -359,53 +361,53 @@
} }
}); });
} }
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil]; cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
return YES; return YES;
} }
- (void)applicationDidBecomeActive:(UIApplication *)application { - (void)applicationDidBecomeActive:(UIApplication *)application {
if ([[MPiOSConfig get].showQuickStart boolValue]) if ([[MPiOSConfig get].showQuickStart boolValue])
[self showGuide]; [self showGuide];
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated]; [TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
[super applicationDidBecomeActive:application]; [super applicationDidBecomeActive:application];
} }
- (void)applicationDidEnterBackground:(UIApplication *)application { - (void)applicationDidEnterBackground:(UIApplication *)application {
[[LocalyticsSession sharedLocalyticsSession] close]; [[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload]; [[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationDidEnterBackground:application]; [super applicationDidEnterBackground:application];
} }
- (void)applicationWillEnterForeground:(UIApplication *)application { - (void)applicationWillEnterForeground:(UIApplication *)application {
[[LocalyticsSession sharedLocalyticsSession] resume]; [[LocalyticsSession sharedLocalyticsSession] resume];
[[LocalyticsSession sharedLocalyticsSession] upload]; [[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationWillEnterForeground:application]; [super applicationWillEnterForeground:application];
} }
- (void)applicationWillTerminate:(UIApplication *)application { - (void)applicationWillTerminate:(UIApplication *)application {
[self saveContext]; [self saveContext];
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated]; [TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
[[LocalyticsSession sharedLocalyticsSession] close]; [[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload]; [[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationWillTerminate:application]; [super applicationWillTerminate:application];
} }
- (void)applicationWillResignActive:(UIApplication *)application { - (void)applicationWillResignActive:(UIApplication *)application {
[self saveContext]; [self saveContext];
if (![[MPiOSConfig get].rememberLogin boolValue]) if (![[MPiOSConfig get].rememberLogin boolValue])
[self signOut]; [self signOut];
@ -416,70 +418,71 @@
- (void)mailComposeController:(MFMailComposeViewController *)controller - (void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
if (error) if (error)
err(@"Error composing mail message: %@", error); err(@"Error composing mail message: %@", error);
switch (result) { switch (result) {
case MFMailComposeResultSaved: case MFMailComposeResultSaved:
case MFMailComposeResultSent: case MFMailComposeResultSent:
break; break;
case MFMailComposeResultFailed: case MFMailComposeResultFailed:
[PearlAlert showError:@"A problem occurred while sending the message." tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { [PearlAlert showError:@"A problem occurred while sending the message."
if (buttonIndex == [alert firstOtherButtonIndex]) tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
return; if (buttonIndex == [alert firstOtherButtonIndex])
} otherTitles:@"Retry", nil]; return;
} otherTitles:@"Retry", nil];
return; return;
case MFMailComposeResultCancelled: case MFMailComposeResultCancelled:
break; break;
} }
[controller dismissModalViewControllerAnimated:YES]; [controller dismissModalViewControllerAnimated:YES];
} }
#pragma mark - UbiquityStoreManagerDelegate #pragma mark - UbiquityStoreManagerDelegate
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
[super ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; [super ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
if (![[MPConfig get].iCloudDecided boolValue]) { if (![[MPConfig get].iCloudDecided boolValue]) {
if (!iCloudEnabled) { if (!iCloudEnabled) {
[PearlAlert showAlertWithTitle:@"iCloud" [PearlAlert showAlertWithTitle:@"iCloud"
message: message:
@"iCloud is now disabled.\n\n" @"iCloud is now disabled.\n\n"
@"It is highly recommended you enable iCloud." @"It is highly recommended you enable iCloud."
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { viewStyle:UIAlertViewStyleDefault initAlert:nil
if (buttonIndex == [alert firstOtherButtonIndex] + 0) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[PearlAlert showAlertWithTitle:@"About iCloud" if (buttonIndex == [alert firstOtherButtonIndex] + 0) {
message: [PearlAlert showAlertWithTitle:@"About iCloud"
@"iCloud is Apple's solution for saving your data in \"the cloud\" " message:
@"and making sure your other iPhones, iPads and Macs are in sync.\n\n" @"iCloud is Apple's solution for saving your data in \"the cloud\" "
@"For Master Password, that means your sites are available on all your " @"and making sure your other iPhones, iPads and Macs are in sync.\n\n"
@"Apple devices, and you always have a backup of them in case " @"For Master Password, that means your sites are available on all your "
@"you loose one or need to restore.\n\n" @"Apple devices, and you always have a backup of them in case "
@"Because of the way Master Password works, it doesn't need to send your " @"you loose one or need to restore.\n\n"
@"site's passwords to Apple. Only their names are saved to make it easier " @"Because of the way Master Password works, it doesn't need to send your "
@"for you to find the site you need. For some sites you may have set " @"site's passwords to Apple. Only their names are saved to make it easier "
@"a user-specified password: these are sent to iCloud after being encrypted " @"for you to find the site you need. For some sites you may have set "
@"with your master password.\n\n" @"a user-specified password: these are sent to iCloud after being encrypted "
@"Apple can never see any of your passwords." @"with your master password.\n\n"
viewStyle:UIAlertViewStyleDefault @"Apple can never see any of your passwords."
initAlert:nil viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; [self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
} }
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil]; cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
return; return;
} }
[MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES]; [MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES];
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
if (buttonIndex == [alert firstOtherButtonIndex] + 1) if (buttonIndex == [alert firstOtherButtonIndex] + 1)
[manager useiCloudStore:YES alertUser:NO]; [manager useiCloudStore:YES alertUser:NO];
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil]; } cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
} }
} }
} }
@ -488,17 +491,17 @@
- (NSDictionary *)testFlightInfo { - (NSDictionary *)testFlightInfo {
static NSDictionary *testFlightInfo = nil; static NSDictionary *testFlightInfo = nil;
if (testFlightInfo == nil) if (testFlightInfo == nil)
testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL: testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]]; [[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
return testFlightInfo; return testFlightInfo;
} }
- (NSString *)testFlightToken { - (NSString *)testFlightToken {
return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]); return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
} }
@ -507,17 +510,17 @@
- (NSDictionary *)crashlyticsInfo { - (NSDictionary *)crashlyticsInfo {
static NSDictionary *crashlyticsInfo = nil; static NSDictionary *crashlyticsInfo = nil;
if (crashlyticsInfo == nil) if (crashlyticsInfo == nil)
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL: crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]]; [[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
return crashlyticsInfo; return crashlyticsInfo;
} }
- (NSString *)crashlyticsAPIKey { - (NSString *)crashlyticsAPIKey {
return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]); return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
} }
@ -526,17 +529,17 @@
- (NSDictionary *)apptentiveInfo { - (NSDictionary *)apptentiveInfo {
static NSDictionary *apptentiveInfo = nil; static NSDictionary *apptentiveInfo = nil;
if (apptentiveInfo == nil) if (apptentiveInfo == nil)
apptentiveInfo = [[NSDictionary alloc] initWithContentsOfURL: apptentiveInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Apptentive" withExtension:@"plist"]]; [[NSBundle mainBundle] URLForResource:@"Apptentive" withExtension:@"plist"]];
return apptentiveInfo; return apptentiveInfo;
} }
- (NSString *)apptentiveAPIKey { - (NSString *)apptentiveAPIKey {
return NSNullToNil([[self apptentiveInfo] valueForKeyPath:@"API Key"]); return NSNullToNil([[self apptentiveInfo] valueForKeyPath:@"API Key"]);
} }
@ -545,17 +548,17 @@
- (NSDictionary *)localyticsInfo { - (NSDictionary *)localyticsInfo {
static NSDictionary *localyticsInfo = nil; static NSDictionary *localyticsInfo = nil;
if (localyticsInfo == nil) if (localyticsInfo == nil)
localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL: localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]]; [[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
return localyticsInfo; return localyticsInfo;
} }
- (NSString *)localyticsKey { - (NSString *)localyticsKey {
#ifdef DEBUG #ifdef DEBUG
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]); return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
#else #else

View File

@ -7,28 +7,27 @@
// //
#import "MPGuideViewController.h" #import "MPGuideViewController.h"
#import "MPAppDelegate.h"
@implementation MPGuideViewController @implementation MPGuideViewController
@synthesize scrollView; @synthesize scrollView;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait); return (interfaceOrientation == UIInterfaceOrientationPortrait);
} }
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
[self.scrollView autoSizeContent]; [self.scrollView autoSizeContent];
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO]; [MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
} }

View File

@ -10,28 +10,28 @@
#import "MPElementEntity.h" #import "MPElementEntity.h"
#import "MPSearchDelegate.h" #import "MPSearchDelegate.h"
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate> @interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate>
@property (strong, nonatomic) MPElementEntity *activeElement; @property (strong, nonatomic) MPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController; @property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
@property (weak, nonatomic) IBOutlet UITextField *contentField; @property (weak, nonatomic) IBOutlet UITextField *contentField;
@property (weak, nonatomic) IBOutlet UIButton *typeButton; @property (weak, nonatomic) IBOutlet UIButton *typeButton;
@property (weak, nonatomic) IBOutlet UIWebView *helpView; @property (weak, nonatomic) IBOutlet UIWebView *helpView;
@property (weak, nonatomic) IBOutlet UILabel *siteName; @property (weak, nonatomic) IBOutlet UILabel *siteName;
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter; @property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer; @property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit; @property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
@property (weak, nonatomic) IBOutlet UIView *contentContainer; @property (weak, nonatomic) IBOutlet UIView *contentContainer;
@property (weak, nonatomic) IBOutlet UIView *helpContainer; @property (weak, nonatomic) IBOutlet UIView *helpContainer;
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer; @property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
@property (weak, nonatomic) IBOutlet UIView *alertContainer; @property (weak, nonatomic) IBOutlet UIView *alertContainer;
@property (weak, nonatomic) IBOutlet UILabel *alertTitle; @property (weak, nonatomic) IBOutlet UILabel *alertTitle;
@property (weak, nonatomic) IBOutlet UITextView *alertBody; @property (weak, nonatomic) IBOutlet UITextView *alertBody;
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody; @property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon; @property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer; @property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer; @property (weak, nonatomic) IBOutlet UIView *actionsTipContainer;
@property (weak, nonatomic) IBOutlet UIView *typeTipContainer; @property (weak, nonatomic) IBOutlet UIView *typeTipContainer;
@property (copy) void (^contentTipCleanup)(BOOL finished); @property (copy) void (^contentTipCleanup)(BOOL finished);

View File

@ -12,8 +12,6 @@
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "ATConnect.h" #import "ATConnect.h"
#import <MobileCoreServices/MobileCoreServices.h>
@interface MPMainViewController (Private) @interface MPMainViewController (Private)
@ -51,51 +49,51 @@
#pragma mark - View lifecycle #pragma mark - View lifecycle
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown; return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
} }
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self setHelpHidden:![self isHelpVisible] animated:NO]; [self setHelpHidden:![self isHelpVisible] animated:NO];
} }
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:@"MP_Main_ChooseType"]) if ([[segue identifier] isEqualToString:@"MP_Main_ChooseType"])
((MPTypeViewController *)[segue destinationViewController]).delegate = self; ((MPTypeViewController *)[segue destinationViewController]).delegate = self;
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; [super viewWillAppear:animated];
if (![MPAppDelegate get].activeUser) if (![MPAppDelegate get].activeUser)
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:self]; [self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:self];
if (self.activeElement.user != [MPAppDelegate get].activeUser) if (self.activeElement.user != [MPAppDelegate get].activeUser)
self.activeElement = nil; self.activeElement = nil;
self.searchDisplayController.searchBar.text = nil; self.searchDisplayController.searchBar.text = nil;
self.searchTipContainer.alpha = 0; self.searchTipContainer.alpha = 0;
self.actionsTipContainer.alpha = 0; self.actionsTipContainer.alpha = 0;
self.typeTipContainer.alpha = 0; self.typeTipContainer.alpha = 0;
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated]; [self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
[self updateAnimated:animated]; [self updateAnimated:animated];
} }
- (void)viewDidAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated {
if ([[MPiOSConfig get].firstRun boolValue]) if ([[MPiOSConfig get].firstRun boolValue])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{ [UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.actionsTipContainer.alpha = 1; self.actionsTipContainer.alpha = 1;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) { if (finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.2f animations:^{
self.actionsTipContainer.alpha = 0; self.actionsTipContainer.alpha = 0;
} completion:^(BOOL finished_) { } completion:^(BOOL finished_) {
if (![self.activeElement.name length]) if (![self.activeElement.name length])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{ [UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.searchTipContainer.alpha = 1; self.searchTipContainer.alpha = 1;
@ -104,26 +102,26 @@
}); });
} }
}]; }];
[[MPAppDelegate get] checkConfig]; [[MPAppDelegate get] checkConfig];
[super viewDidAppear:animated]; [super viewDidAppear:animated];
} }
- (void)viewDidLoad { - (void)viewDidLoad {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]]; self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize]; self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize];
self.alertBody.text = nil; self.alertBody.text = nil;
self.contentTipEditIcon.hidden = YES; self.contentTipEditIcon.hidden = YES;
[super viewDidLoad]; [super viewDidLoad];
} }
- (void)viewDidUnload { - (void)viewDidUnload {
[self setContentField:nil]; [self setContentField:nil];
[self setTypeButton:nil]; [self setTypeButton:nil];
[self setSearchResultsController:nil]; [self setSearchResultsController:nil];
@ -147,35 +145,35 @@
} }
- (void)updateAnimated:(BOOL)animated { - (void)updateAnimated:(BOOL)animated {
if (animated) { if (animated) {
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
[self updateAnimated:NO]; [self updateAnimated:NO];
}]; }];
return; return;
} }
[self setHelpChapter:self.activeElement? @"2": @"1"]; [self setHelpChapter:self.activeElement? @"2": @"1"];
self.siteName.text = self.activeElement.name; 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.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) [self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
forState:UIControlStateNormal]; forState:UIControlStateNormal];
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0; self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
self.contentField.enabled = NO; self.contentField.enabled = NO;
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) 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 = @""; self.contentField.text = @"";
if (self.activeElement.name && ![self.activeElement isDeleted]) if (self.activeElement.name && ![self.activeElement isDeleted])
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [self.activeElement.content description]; NSString *description = [self.activeElement.content description];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
self.contentField.text = description; self.contentField.text = description;
}); });
@ -183,26 +181,26 @@
} }
- (BOOL)isHelpVisible { - (BOOL)isHelpVisible {
return self.helpContainer.frame.origin.y == 216; return self.helpContainer.frame.origin.y == 216;
} }
- (void)toggleHelpAnimated:(BOOL)animated { - (void)toggleHelpAnimated:(BOOL)animated {
[self setHelpHidden:[self isHelpVisible] animated:animated]; [self setHelpHidden:[self isHelpVisible] animated:animated];
} }
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated { - (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{ [UIView animateWithDuration:animated? 0.3f: 0 animations:^{
if (hidden) { if (hidden) {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44); 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.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES]; [MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES];
} else { } else {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175); self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216); self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO]; [MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO];
} }
}]; }];
@ -210,9 +208,9 @@
} }
- (void)setHelpChapter:(NSString *)chapter { - (void)setHelpChapter:(NSString *)chapter {
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]]; [TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url = [NSURL URLWithString:[@"#" stringByAppendingString:chapter] NSURL *url = [NSURL URLWithString:[@"#" stringByAppendingString:chapter]
relativeToURL:[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]; relativeToURL:[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]];
@ -221,35 +219,35 @@
} }
- (void)webViewDidFinishLoad:(UIWebView *)webView { - (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString: NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))]; PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
if (error.length) if (error.length)
err(@"helpView.setClass: %@", error); err(@"helpView.setClass: %@", error);
} }
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon { - (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (self.contentTipCleanup) if (self.contentTipCleanup)
self.contentTipCleanup(NO); self.contentTipCleanup(NO);
self.contentTipBody.text = message; self.contentTipBody.text = message;
self.contentTipCleanup = ^(BOOL finished) { self.contentTipCleanup = ^(BOOL finished) {
icon.hidden = YES; icon.hidden = YES;
self.contentTipCleanup = nil; self.contentTipCleanup = nil;
}; };
icon.hidden = NO; icon.hidden = NO;
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
self.contentTipContainer.alpha = 1; self.contentTipContainer.alpha = 1;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) { if (finished) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC); 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:^{ [UIView animateWithDuration:0.2f animations:^{
self.contentTipContainer.alpha = 0; self.contentTipContainer.alpha = 0;
} completion:self.contentTipCleanup]; } completion:self.contentTipCleanup];
}); });
} }
}]; }];
@ -257,7 +255,7 @@
} }
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message { - (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
self.alertTitle.text = title; self.alertTitle.text = title;
NSRange scrollRange = NSMakeRange(self.alertBody.text.length, message.length); NSRange scrollRange = NSMakeRange(self.alertBody.text.length, message.length);
@ -266,7 +264,7 @@
else else
self.alertBody.text = message; self.alertBody.text = message;
[self.alertBody scrollRangeToVisible:scrollRange]; [self.alertBody scrollRangeToVisible:scrollRange];
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
self.alertContainer.alpha = 1; self.alertContainer.alpha = 1;
}]; }];
@ -276,119 +274,118 @@
#pragma mark - Protocols #pragma mark - Protocols
- (IBAction)copyContent { - (IBAction)copyContent {
if (!self.activeElement) if (!self.activeElement)
return; return;
[UIPasteboard generalPasteboard].string = [self.activeElement.content description]; [UIPasteboard generalPasteboard].string = [self.activeElement.content description];
[self showContentTip:@"Copied!" withIcon:nil]; [self showContentTip:@"Copied!" withIcon:nil];
[TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard]; [TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard];
} }
- (IBAction)incrementPasswordCounter { - (IBAction)incrementPasswordCounter {
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) 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; return;
[self changeElementWithWarning: [self changeElementWithWarning:
@"You are incrementing the site's password counter.\n\n" @"You are incrementing the site's password counter.\n\n"
@"If you continue, a new password will be generated for this site. " @"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 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 can reset the counter by holding down on this button."
do:^{ do:^{
++((MPElementGeneratedEntity *) self.activeElement).counter; ++((MPElementGeneratedEntity *)self.activeElement).counter;
}]; }];
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter]; [TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter];
} }
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender { - (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) if (sender.state != UIGestureRecognizerStateBegan)
// Only fire when the gesture was first detected. // Only fire when the gesture was first detected.
return; return;
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) 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; return;
if (((MPElementGeneratedEntity *)self.activeElement).counter == 1) if (((MPElementGeneratedEntity *)self.activeElement).counter == 1)
// Counter has initial value, no point resetting. // Counter has initial value, no point resetting.
return; return;
[self changeElementWithWarning: [self changeElementWithWarning:
@"You are resetting the site's password counter.\n\n" @"You are resetting the site's password counter.\n\n"
@"If you continue, the site's password will change back to its original value. " @"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 will then need to update your account's password back to this original value."
do:^{ do:^{
((MPElementGeneratedEntity *) self.activeElement).counter = 1; ((MPElementGeneratedEntity *)self.activeElement).counter = 1;
}]; }];
[TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter]; [TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter];
} }
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; { - (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault [PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
initAlert:nil initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { if (buttonIndex == [alert cancelButtonIndex])
if (buttonIndex == [alert cancelButtonIndex]) return;
return;
[self changeElementWithoutWarningDo:task];
[self changeElementWithoutWarningDo:task]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
} }
- (void)changeElementWithoutWarningDo:(void (^)(void))task; { - (void)changeElementWithoutWarningDo:(void (^)(void))task; {
// Update element, keeping track of the old password. // Update element, keeping track of the old password.
NSString *oldPassword = [self.activeElement.content description]; NSString *oldPassword = [self.activeElement.content description];
task(); task();
NSString *newPassword = [self.activeElement.content description]; NSString *newPassword = [self.activeElement.content description];
[[MPAppDelegate get] saveContext]; [[MPAppDelegate get] saveContext];
[self updateAnimated:YES]; [self updateAnimated:YES];
// Show new and old password. // Show new and old password.
if ([oldPassword length] && ![oldPassword isEqualToString:newPassword]) if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
[self showAlertWithTitle:@"Password Changed!" [self showAlertWithTitle:@"Password Changed!"
message:PearlString(@"The password for %@ has changed.\n\n" message:PearlString(@"The password for %@ has changed.\n\n"
@"IMPORTANT:\n" @"IMPORTANT:\n"
@"Don't forget to update the site with your new password! " @"Don't forget to update the site with your new password! "
@"Your old password was:\n" @"Your old password was:\n"
@"%@", self.activeElement.name, oldPassword)]; @"%@", self.activeElement.name, oldPassword)];
} }
- (IBAction)editPassword { - (IBAction)editPassword {
if (self.activeElement.type & MPElementTypeClassStored) { if (self.activeElement.type & MPElementTypeClassStored) {
self.contentField.enabled = YES; self.contentField.enabled = YES;
[self.contentField becomeFirstResponder]; [self.contentField becomeFirstResponder];
} }
[TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword]; [TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword];
} }
- (IBAction)closeAlert { - (IBAction)closeAlert {
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
self.alertContainer.alpha = 0; self.alertContainer.alpha = 0;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) if (finished)
self.alertBody.text = nil; self.alertBody.text = nil;
}]; }];
[TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert]; [TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert];
} }
- (IBAction)action:(id)sender { - (IBAction)action:(id)sender {
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic [PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex]) if (buttonIndex == [sheet cancelButtonIndex])
return; return;
switch (buttonIndex - [sheet firstOtherButtonIndex]) { switch (buttonIndex - [sheet firstOtherButtonIndex]) {
case 0: { case 0: {
[self toggleHelpAnimated:YES]; [self toggleHelpAnimated:YES];
@ -427,68 +424,70 @@
break; break;
} }
} }
[TestFlight passCheckpoint:MPTestFlightCheckpointAction]; [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]; [self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
} }
- (MPElementType)selectedType { - (MPElementType)selectedType {
return self.activeElement.type; return self.activeElement.type;
} }
- (void)didSelectType:(MPElementType)type { - (void)didSelectType:(MPElementType)type {
[self changeElementWithWarning: [self changeElementWithWarning:
@"You are about to change the type of this password.\n\n" @"You are about to change the type of this password.\n\n"
@"If you continue, the password for this site will change. " @"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 will need to update your account's old password to the new one."
do:^{ do:^{
// Update password type. // Update password type.
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.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:^{ [[MPAppDelegate managedObjectContext] performBlockAndWait:^{
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(
type)
inManagedObjectContext:[MPAppDelegate managedObjectContext]]; inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name; newElement.name = self.activeElement.name;
newElement.user = self.activeElement.user; newElement.user = self.activeElement.user;
newElement.uses = self.activeElement.uses; newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed; newElement.lastUsed = self.activeElement.lastUsed;
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement]; [[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
self.activeElement = newElement; self.activeElement = newElement;
}]; }];
self.activeElement.type = type; 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]) if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon]; [self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}]; }];
} }
- (void)didSelectElement:(MPElementEntity *)element { - (void)didSelectElement:(MPElementEntity *)element {
[self closeAlert]; [self closeAlert];
if (element) { if (element) {
self.activeElement = element; self.activeElement = element;
if ([self.activeElement use] == 1) if ([self.activeElement use] == 1)
[self showAlertWithTitle:@"New Site" message: [self showAlertWithTitle:@"New Site" message:
PearlString(@"You've just created a password for %@.\n\n" PearlString(@"You've just created a password for %@.\n\n"
@"IMPORTANT:\n" @"IMPORTANT:\n"
@"Go to %@ and set or change the password for your account to the password above.\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.", @"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)]; self.activeElement.name, self.activeElement.name)];
[[MPAppDelegate get] saveContext]; [[MPAppDelegate get] saveContext];
if ([[MPiOSConfig get].firstRun boolValue]) if ([[MPiOSConfig get].firstRun boolValue])
[UIView animateWithDuration:0.5f animations:^{ [UIView animateWithDuration:0.5f animations:^{
self.typeTipContainer.alpha = 1; self.typeTipContainer.alpha = 1;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) { if (finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.2f animations:^{
@ -497,53 +496,53 @@
}); });
} }
}]; }];
[self.searchDisplayController setActive:NO animated:YES]; [self.searchDisplayController setActive:NO animated:YES];
self.searchDisplayController.searchBar.text = self.activeElement.name; self.searchDisplayController.searchBar.text = self.activeElement.name;
[TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement]; [TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement]; [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement];
} }
[self updateAnimated:YES]; [self updateAnimated:YES];
} }
- (BOOL)textFieldShouldReturn:(UITextField *)textField { - (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == self.contentField) if (textField == self.contentField)
[self.contentField resignFirstResponder]; [self.contentField resignFirstResponder];
return YES; return YES;
} }
- (void)textFieldDidEndEditing:(UITextField *)textField { - (void)textFieldDidEndEditing:(UITextField *)textField {
if (textField == self.contentField) { if (textField == self.contentField) {
self.contentField.enabled = NO; self.contentField.enabled = NO;
if (![self.activeElement isKindOfClass:[MPElementStoredEntity class]]) 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; return;
if ([((MPElementStoredEntity *) self.activeElement).content isEqual:self.contentField.text]) if ([((MPElementStoredEntity *)self.activeElement).content isEqual:self.contentField.text])
// Content hasn't changed. // Content hasn't changed.
return; return;
[self changeElementWithoutWarningDo:^{ [self changeElementWithoutWarningDo:^{
((MPElementStoredEntity *) self.activeElement).content = self.contentField.text; ((MPElementStoredEntity *)self.activeElement).content = self.contentField.text;
}]; }];
} }
} }
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType { navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) { if (navigationType == UIWebViewNavigationTypeLinkClicked) {
[TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink]; [TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink];
[[UIApplication sharedApplication] openURL:[request URL]]; [[UIApplication sharedApplication] openURL:[request URL]];
return NO; return NO;
} }
return YES; return YES;
} }

View File

@ -9,11 +9,11 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "IASKAppSettingsViewController.h" #import "IASKAppSettingsViewController.h"
@interface MPPreferencesViewController : UITableViewController <IASKSettingsDelegate> @interface MPPreferencesViewController : UITableViewController<IASKSettingsDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView; @property (weak, nonatomic) IBOutlet UIScrollView *avatarsView;
@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate; @property (weak, nonatomic) IBOutlet UIButton *avatarTemplate;
@property (weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch; @property (weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch;
@property (weak, nonatomic) IBOutlet UITableViewCell *exportCell; @property (weak, nonatomic) IBOutlet UITableViewCell *exportCell;
@property (weak, nonatomic) IBOutlet UITableViewCell *changeMPCell; @property (weak, nonatomic) IBOutlet UITableViewCell *changeMPCell;

View File

@ -24,25 +24,25 @@
- (void)viewDidLoad { - (void)viewDidLoad {
self.avatarTemplate.hidden = YES; self.avatarTemplate.hidden = YES;
for (int a = 0; a < MPAvatarCount; ++a) { for (int a = 0; a < MPAvatarCount; ++a) {
UIButton *avatar = [self.avatarTemplate clone]; UIButton *avatar = [self.avatarTemplate clone];
avatar.togglesSelectionInSuperview = YES; avatar.togglesSelectionInSuperview = YES;
avatar.tag = a; avatar.tag = a;
avatar.hidden = NO; avatar.hidden = NO;
avatar.center = CGPointMake( avatar.center = CGPointMake(
self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a, self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
self.avatarTemplate.center.y); self.avatarTemplate.center.y);
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)] [avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)]
forState:UIControlStateNormal]; forState:UIControlStateNormal];
avatar.layer.cornerRadius = avatar.bounds.size.height / 2; avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor; avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1; avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 5; avatar.layer.shadowRadius = 5;
avatar.backgroundColor = [UIColor clearColor]; avatar.backgroundColor = [UIColor clearColor];
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) { [avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected) if (highlighted || selected)
@ -54,32 +54,33 @@
if (selected) if (selected)
[MPAppDelegate get].activeUser.avatar = (unsigned)avatar.tag; [MPAppDelegate get].activeUser.avatar = (unsigned)avatar.tag;
} options:0]; } options:0];
avatar.selected = (a == [MPAppDelegate get].activeUser.avatar); avatar.selected = (a == [MPAppDelegate get].activeUser.avatar);
} }
[super viewDidLoad]; [super viewDidLoad];
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[self.avatarsView autoSizeContent]; [self.avatarsView autoSizeContent];
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [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]; [self.avatarsView setContentOffset:CGPointMake(subview.center.x - self.avatarsView.bounds.size.width / 2, 0) animated:animated];
} }
} recurse:NO]; } recurse:NO];
self.savePasswordSwitch.on = [MPAppDelegate get].activeUser.saveKey; self.savePasswordSwitch.on = [MPAppDelegate get].activeUser.saveKey;
[super viewWillAppear:animated]; [super viewWillAppear:animated];
} }
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait); return (interfaceOrientation == UIInterfaceOrientationPortrait);
} }
- (void)viewDidUnload { - (void)viewDidUnload {
[self setAvatarsView:nil]; [self setAvatarsView:nil];
[self setAvatarTemplate:nil]; [self setAvatarTemplate:nil];
[self setAvatarsView:nil]; [self setAvatarsView:nil];
@ -92,13 +93,14 @@
#pragma mark - UITableViewDelegate #pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath]; UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell == self.exportCell) if (cell == self.exportCell)
[[MPAppDelegate get] export]; [[MPAppDelegate get] export];
else if (cell == self.changeMPCell) else
[[MPAppDelegate get] changeMP]; if (cell == self.changeMPCell)
[[MPAppDelegate get] changeMP];
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
} }
@ -106,7 +108,7 @@
#pragma mark - IASKSettingsDelegate #pragma mark - IASKSettingsDelegate
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender { - (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
while ([self.navigationController.viewControllers containsObject:sender]) while ([self.navigationController.viewControllers containsObject:sender])
[self.navigationController popViewControllerAnimated:YES]; [self.navigationController popViewControllerAnimated:YES];
} }
@ -114,7 +116,7 @@
#pragma mark - IBActions #pragma mark - IBActions
- (IBAction)didToggleSwitch:(UISwitch *)sender { - (IBAction)didToggleSwitch:(UISwitch *)sender {
if (([MPAppDelegate get].activeUser.saveKey = sender.on)) if (([MPAppDelegate get].activeUser.saveKey = sender.on))
[[MPAppDelegate get] storeSavedKeyFor:[MPAppDelegate get].activeUser]; [[MPAppDelegate get] storeSavedKeyFor:[MPAppDelegate get].activeUser];
else else

View File

@ -9,21 +9,21 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "MPElementEntity.h" #import "MPElementEntity.h"
@protocol MPSearchResultsDelegate <NSObject> @protocol MPSearchResultsDelegate<NSObject>
- (void)didSelectElement:(MPElementEntity *)element; - (void)didSelectElement:(MPElementEntity *)element;
@end @end
@interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate> @interface MPSearchDelegate : NSObject<UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
@property (strong, nonatomic) NSDateFormatter *dateFormatter; @property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController; @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSString *query; @property (strong, nonatomic) NSString *query;
@property (strong, nonatomic) UILabel *tipView; @property (strong, nonatomic) UILabel *tipView;
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate; @property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController; @property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer; @property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@end @end

View File

@ -26,52 +26,53 @@
@synthesize searchTipContainer; @synthesize searchTipContainer;
- (id)init { - (id)init {
if (!([super init])) if (!([super init]))
return nil; return nil;
self.dateFormatter = [NSDateFormatter new]; self.dateFormatter = [NSDateFormatter new];
self.dateFormatter.dateStyle = NSDateFormatterShortStyle; self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
self.query = @""; self.query = @"";
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
self.fetchedResultsController = [PearlLazy lazyObjectLoadedFrom:^id{ self.fetchedResultsController = [PearlLazy lazyObjectLoadedFrom:^id {
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext] managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil]; sectionNameKeyPath:nil cacheName:nil];
controller.delegate = self; controller.delegate = self;
return controller; return controller;
}]; }];
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)]; self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
self.tipView.textAlignment = UITextAlignmentCenter; self.tipView.textAlignment = UITextAlignmentCenter;
self.tipView.backgroundColor = [UIColor clearColor]; self.tipView.backgroundColor = [UIColor clearColor];
self.tipView.textColor = [UIColor lightTextColor]; self.tipView.textColor = [UIColor lightTextColor];
self.tipView.shadowColor = [UIColor blackColor]; self.tipView.shadowColor = [UIColor blackColor];
self.tipView.shadowOffset = CGSizeMake(0, -1); self.tipView.shadowOffset = CGSizeMake(0, -1);
self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin; self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
self.tipView.numberOfLines = 0; | UIViewAutoresizingFlexibleBottomMargin;
self.tipView.font = [UIFont systemFontOfSize:14]; self.tipView.numberOfLines = 0;
self.tipView.font = [UIFont systemFontOfSize:14];
self.tipView.text = self.tipView.text =
@"Tip:\n" @"Tip:\n"
@"Name your sites by their domain name:\n" @"Name your sites by their domain name:\n"
@"apple.com, twitter.com\n\n" @"apple.com, twitter.com\n\n"
@"For email accounts, use the address:\n" @"For email accounts, use the address:\n"
@"john@apple.com, john@gmail.com"; @"john@apple.com, john@gmail.com";
return self; return self;
} }
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
UITableView *tableView = self.searchDisplayController.searchResultsTableView; UITableView *tableView = self.searchDisplayController.searchResultsTableView;
for (NSInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) { for (NSInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
NSInteger rowCount = [self tableView:tableView numberOfRowsInSection:section]; NSInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
if (!rowCount) if (!rowCount)
continue; continue;
if (rowCount == 1) if (rowCount == 1)
[self tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]]; [self tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
break; break;
@ -79,64 +80,64 @@
} }
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch]; [TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
[self.delegate didSelectElement:nil]; [self.delegate didSelectElement:nil];
} }
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchBar.searchResultsButtonSelected && !searchText.length) if (searchBar.searchResultsButtonSelected && !searchText.length)
searchBar.text = @" "; searchBar.text = @" ";
self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (!self.query) if (!self.query)
self.query = @""; self.query = @"";
} }
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller { - (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.prompt = @"Enter the site's name:"; controller.searchBar.prompt = @"Enter the site's name:";
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.2f animations:^{
self.searchTipContainer.alpha = 0; self.searchTipContainer.alpha = 0;
}]; }];
} }
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller { - (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @""; controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
self.query = @""; self.query = @"";
} }
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller { - (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
controller.searchBar.prompt = nil; controller.searchBar.prompt = nil;
controller.searchBar.searchResultsButtonSelected = NO; controller.searchBar.searchResultsButtonSelected = NO;
} }
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView { - (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
tableView.backgroundColor = [UIColor blackColor]; tableView.backgroundColor = [UIColor blackColor];
tableView.separatorStyle = UITableViewCellSeparatorStyleNone; tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView.rowHeight = 48.0f; tableView.rowHeight = 48.0f;
} }
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
if (!controller.active) if (!controller.active)
return NO; return NO;
assert(self.query); assert(self.query);
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@", 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; NSError *error;
if (![self.fetchedResultsController performFetch:&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; NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews;
NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1; NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1;
UIView *overlay = [subviews count] > overlayIndex? [subviews objectAtIndex:overlayIndex]: nil; UIView *overlay = [subviews count] > overlayIndex? [subviews objectAtIndex:overlayIndex]: nil;
@ -146,7 +147,7 @@
[self.tipView removeFromSuperview]; [self.tipView removeFromSuperview];
[overlay addSubview:self.tipView]; [overlay addSubview:self.tipView];
} }
return YES; return YES;
} }
@ -204,16 +205,16 @@
//} //}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.searchDisplayController.searchResultsTableView reloadData]; [self.searchDisplayController.searchResultsTableView reloadData];
// [self.searchDisplayController.searchResultsTableView endUpdates]; // [self.searchDisplayController.searchResultsTableView endUpdates];
} }
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSArray *sections = [self.fetchedResultsController sections]; NSArray *sections = [self.fetchedResultsController sections];
NSUInteger sectionCount = [sections count]; NSUInteger sectionCount = [sections count];
if ([self.query length]) { if ([self.query length]) {
__block BOOL hasExactQueryMatch = NO; __block BOOL hasExactQueryMatch = NO;
[sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
@ -225,129 +226,130 @@
} }
}]; }];
if (hasExactQueryMatch) if (hasExactQueryMatch)
*stop = YES; *stop = YES;
}]; }];
if (!hasExactQueryMatch) if (!hasExactQueryMatch)
// Add a section for "new site". // Add a section for "new site".
++sectionCount; ++sectionCount;
} }
return (NSInteger)sectionCount; return (NSInteger)sectionCount;
} }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *sections = [self.fetchedResultsController sections]; NSArray *sections = [self.fetchedResultsController sections];
if (section < (NSInteger)[sections count]) if (section < (NSInteger)[sections count])
return (NSInteger)[[sections objectAtIndex:(unsigned)section] numberOfObjects]; return (NSInteger)[[sections objectAtIndex:(unsigned)section] numberOfObjects];
return 1; return 1;
} }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"]; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"];
if (!cell) { if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"]; cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]]; 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.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)]; UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)];
[backgroundView addSubview:backgroundImageView]; [backgroundView addSubview:backgroundImageView];
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.backgroundView = backgroundView; cell.backgroundView = backgroundView;
cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor]; cell.textLabel.textColor = [UIColor whiteColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor]; cell.detailTextLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor = [UIColor lightGrayColor]; cell.detailTextLabel.textColor = [UIColor lightGrayColor];
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.clipsToBounds = YES; cell.clipsToBounds = YES;
} }
[self configureCell:cell inTableView:tableView atIndexPath:indexPath]; [self configureCell:cell inTableView:tableView atIndexPath:indexPath];
return cell; return cell;
} }
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath { - (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) { if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) {
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; 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 %@", 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 { } else {
// "New" section // "New" section
cell.textLabel.text = self.query; cell.textLabel.text = self.query;
cell.detailTextLabel.text = @"Create a new site."; cell.detailTextLabel.text = @"Create a new site.";
} }
} }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count])
[self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]]; [self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]];
else { else {
// "New" section. // "New" section.
NSString *siteName = self.query; NSString *siteName = self.query;
[PearlAlert showAlertWithTitle:@"New Site" [PearlAlert showAlertWithTitle:@"New Site"
message:PearlString(@"Do you want to create a new site named:\n%@", siteName) message:PearlString(@"Do you want to create a new site named:\n%@", siteName)
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
initAlert:nil initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { [tableView deselectRowAtIndexPath:indexPath animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (buttonIndex == [alert cancelButtonIndex])
if (buttonIndex == [alert cancelButtonIndex]) return;
return;
[self.fetchedResultsController.managedObjectContext performBlock:^{
[self.fetchedResultsController.managedObjectContext performBlock:^{ MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(
MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class]) [MPElementGeneratedEntity class])
inManagedObjectContext:self.fetchedResultsController.managedObjectContext]; inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([element isKindOfClass:ClassFromMPElementType(element.type)]); assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
assert([MPAppDelegate get].activeUser.keyID); assert([MPAppDelegate get].activeUser.keyID);
element.name = siteName; element.name = siteName;
element.user = [MPAppDelegate get].activeUser; element.user = [MPAppDelegate get].activeUser;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didSelectElement:element]; [self.delegate didSelectElement:element];
}); });
}]; }];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
} }
} }
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (section < (NSInteger)[[self.fetchedResultsController sections] count]) if (section < (NSInteger)[[self.fetchedResultsController sections] count])
return [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name]; return [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name];
return @""; return @"";
} }
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [self.fetchedResultsController sectionIndexTitles]; return [self.fetchedResultsController sectionIndexTitles];
} }
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex: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 (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) {
if (editingStyle == UITableViewCellEditingStyleDelete) if (editingStyle == UITableViewCellEditingStyleDelete)
[self.fetchedResultsController.managedObjectContext performBlock:^{ [self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.fetchedResultsController.managedObjectContext deleteObject:element]; [self.fetchedResultsController.managedObjectContext deleteObject:element];
[TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement]; [TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement];
}]; }];
} }

View File

@ -8,7 +8,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@protocol MPTypeDelegate <NSObject> @protocol MPTypeDelegate<NSObject>
- (void)didSelectType:(MPElementType)type; - (void)didSelectType:(MPElementType)type;

View File

@ -22,16 +22,16 @@
#pragma mark - View lifecycle #pragma mark - View lifecycle
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
self.recommendedTipContainer.alpha = 0; self.recommendedTipContainer.alpha = 0;
} }
- (void)viewDidAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated {
if ([[MPiOSConfig get].firstRun boolValue]) if ([[MPiOSConfig get].firstRun boolValue])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{ [UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.recommendedTipContainer.alpha = 1; self.recommendedTipContainer.alpha = 1;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) { if (finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.2f animations:^{
@ -40,26 +40,26 @@
}); });
} }
}]; }];
[super viewDidAppear:animated]; [super viewDidAppear:animated];
} }
- (void)viewDidLoad { - (void)viewDidLoad {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]]; self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
[super viewDidLoad]; [super viewDidLoad];
} }
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES; return YES;
} }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if ([delegate respondsToSelector:@selector(selectedType)]) if ([delegate respondsToSelector:@selector(selectedType)])
if ([delegate selectedType] == [self typeAtIndexPath:indexPath]) if ([delegate selectedType] == [self typeAtIndexPath:indexPath])
[cell enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [cell enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
@ -67,24 +67,24 @@
UIImageView *imageView = ((UIImageView *)subview); UIImageView *imageView = ((UIImageView *)subview);
if (!imageView.highlightedImage) if (!imageView.highlightedImage)
imageView.highlightedImage = [imageView.image highlightedImage]; imageView.highlightedImage = [imageView.image highlightedImage];
imageView.highlighted = YES; imageView.highlighted = YES;
*stop = YES; *stop = YES;
} }
} recurse:NO]; } recurse:NO];
return cell; return cell;
} }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
assert(self.navigationController.topViewController == self); assert(self.navigationController.topViewController == self);
[delegate didSelectType:[self typeAtIndexPath:indexPath]]; [delegate didSelectType:[self typeAtIndexPath:indexPath]];
[self.navigationController popViewControllerAnimated:YES]; [self.navigationController popViewControllerAnimated:YES];
} }
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath { - (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) { switch (indexPath.section) {
case 0: { case 0: {
// Generated // Generated
@ -103,12 +103,12 @@
return MPElementTypeGeneratedBasic; return MPElementTypeGeneratedBasic;
case 6: case 6:
return MPElementTypeGeneratedPIN; return MPElementTypeGeneratedPIN;
default: default:
Throw(@"Unsupported row: %d, when selecting generated element type.", indexPath.row); Throw(@"Unsupported row: %d, when selecting generated element type.", indexPath.row);
} }
} }
case 1: { case 1: {
// Stored // Stored
switch (indexPath.row) { switch (indexPath.row) {
@ -118,18 +118,19 @@
return MPElementTypeStoredPersonal; return MPElementTypeStoredPersonal;
case 2: case 2:
return MPElementTypeStoredDevicePrivate; return MPElementTypeStoredDevicePrivate;
default: default:
Throw(@"Unsupported row: %d, when selecting stored element type.", indexPath.row); Throw(@"Unsupported row: %d, when selecting stored element type.", indexPath.row);
} }
} }
default: default:
Throw(@"Unsupported section: %d, when selecting element type.", indexPath.section); Throw(@"Unsupported section: %d, when selecting element type.", indexPath.section);
} }
} }
- (void)viewDidUnload { - (void)viewDidUnload {
[self setRecommendedTipContainer:nil]; [self setRecommendedTipContainer:nil];
[super viewDidUnload]; [super viewDidUnload];
} }

View File

@ -8,20 +8,20 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface MPUnlockViewController : UIViewController <UITextFieldDelegate, UIScrollViewDelegate> @interface MPUnlockViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *spinner; @property (weak, nonatomic) IBOutlet UIImageView *spinner;
@property (weak, nonatomic) IBOutlet UITextField *passwordField; @property (weak, nonatomic) IBOutlet UITextField *passwordField;
@property (weak, nonatomic) IBOutlet UIView *passwordView; @property (weak, nonatomic) IBOutlet UIView *passwordView;
@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView; @property (weak, nonatomic) IBOutlet UIScrollView *avatarsView;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel; @property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *oldNameLabel; @property (weak, nonatomic) IBOutlet UILabel *oldNameLabel;
@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate; @property (weak, nonatomic) IBOutlet UIButton *avatarTemplate;
@property (weak, nonatomic) IBOutlet UILabel *deleteTip; @property (weak, nonatomic) IBOutlet UILabel *deleteTip;
@property (weak, nonatomic) IBOutlet UIView *passwordTipView; @property (weak, nonatomic) IBOutlet UIView *passwordTipView;
@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel; @property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel;
@property(nonatomic, strong) UIColor *avatarShadowColor; @property (nonatomic, strong) UIColor *avatarShadowColor;
- (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender; - (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender;

View File

@ -15,8 +15,8 @@
@interface MPUnlockViewController () @interface MPUnlockViewController ()
@property(strong, nonatomic) MPUserEntity *selectedUser; @property (strong, nonatomic) MPUserEntity *selectedUser;
@property(strong, nonatomic) NSMutableDictionary *avatarToUser; @property (strong, nonatomic) NSMutableDictionary *avatarToUser;
@end @end
@ -40,29 +40,29 @@
// } completion:nil]; // } completion:nil];
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait); return (interfaceOrientation == UIInterfaceOrientationPortrait);
} }
- (void)viewDidLoad { - (void)viewDidLoad {
self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3]; self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3];
self.passwordField.text = nil; self.passwordField.text = nil;
self.avatarsView.decelerationRate = UIScrollViewDecelerationRateFast; self.avatarsView.decelerationRate = UIScrollViewDecelerationRateFast;
self.avatarsView.clipsToBounds = NO; self.avatarsView.clipsToBounds = NO;
self.nameLabel.layer.cornerRadius = 5; self.nameLabel.layer.cornerRadius = 5;
self.avatarTemplate.hidden = YES; self.avatarTemplate.hidden = YES;
self.spinner.alpha = 0; self.spinner.alpha = 0;
self.passwordTipView.alpha = 0; self.passwordTipView.alpha = 0;
[self updateLayoutAnimated:NO allowScroll:YES completion:nil]; [self updateLayoutAnimated:NO allowScroll:YES completion:nil];
[super viewDidLoad]; [super viewDidLoad];
} }
- (void)viewDidUnload { - (void)viewDidUnload {
[self setSpinner:nil]; [self setSpinner:nil];
[self setPasswordField:nil]; [self setPasswordField:nil];
[self setPasswordView:nil]; [self setPasswordView:nil];
@ -76,24 +76,24 @@
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
self.selectedUser = nil; self.selectedUser = nil;
[self updateUsers]; [self updateUsers];
[super viewWillAppear:animated]; [super viewWillAppear:animated];
} }
- (void)viewDidAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated ? UIStatusBarAnimationSlide : UIStatusBarAnimationNone]; [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewDidAppear:animated]; [super viewDidAppear:animated];
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:animated ? UIStatusBarAnimationSlide : UIStatusBarAnimationNone]; [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
} }
@ -102,11 +102,11 @@
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil]; NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil];
// Clean up avatars. // Clean up avatars.
for (UIView *subview in [self.avatarsView subviews]) for (UIView *subview in [self.avatarsView subviews])
if ([[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) if ([[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
// This subview is a former avatar. // This subview is a former avatar.
[subview removeFromSuperview]; [subview removeFromSuperview];
[self.avatarToUser removeAllObjects]; [self.avatarToUser removeAllObjects];
@ -117,12 +117,12 @@
// Scroll view's content changed, update its content size. // Scroll view's content changed, update its content size.
[self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil]; [self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
[self updateLayoutAnimated:YES allowScroll:YES completion:nil]; [self updateLayoutAnimated:YES allowScroll:YES completion:nil];
} }
- (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user { - (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user {
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) { [avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected) if (highlighted || selected)
avatar.backgroundColor = self.avatarTemplate.backgroundColor; avatar.backgroundColor = self.avatarTemplate.backgroundColor;
@ -130,43 +130,45 @@
avatar.backgroundColor = [UIColor clearColor]; avatar.backgroundColor = [UIColor clearColor];
} options:0]; } options:0];
[avatar onSelect:^(BOOL selected) { [avatar onSelect:^(BOOL selected) {
self.selectedUser = selected ? user : nil; self.selectedUser = selected? user: nil;
if (user) if (user)
[self didToggleUserSelection]; [self didToggleUserSelection];
else if (selected) else
[self didSelectNewUserAvatar:avatar]; if (selected)
[self didSelectNewUserAvatar:avatar];
} options:0]; } options:0];
avatar.togglesSelectionInSuperview = YES; avatar.togglesSelectionInSuperview = YES;
avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y); avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y);
avatar.hidden = NO; avatar.hidden = NO;
avatar.layer.cornerRadius = avatar.bounds.size.height / 2; avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor; avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1; avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 20; avatar.layer.shadowRadius = 20;
avatar.layer.masksToBounds = NO; avatar.layer.masksToBounds = NO;
avatar.backgroundColor = [UIColor clearColor]; avatar.backgroundColor = [UIColor clearColor];
dbg(@"User: %@, avatar: %d", user.name, user.avatar); dbg(@"User: %@, avatar: %d", user.name, user.avatar);
avatar.tag = user.avatar; avatar.tag = user.avatar;
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)] [avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)]
forState:UIControlStateNormal]; forState:UIControlStateNormal];
[self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]]; [self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]];
if (self.selectedUser && user == self.selectedUser) if (self.selectedUser && user == self.selectedUser)
avatar.selected = YES; avatar.selected = YES;
return avatar; return avatar;
} }
- (void)didToggleUserSelection { - (void)didToggleUserSelection {
if (!self.selectedUser) if (!self.selectedUser)
[self.passwordField resignFirstResponder]; [self.passwordField resignFirstResponder];
else if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) { else
[self dismissModalViewControllerAnimated:YES]; if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) {
return; [self dismissModalViewControllerAnimated:YES];
} return;
}
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) { [self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
if (finished) if (finished)
@ -176,104 +178,105 @@
} }
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar { - (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
[PearlAlert showAlertWithTitle:@"New User" [PearlAlert showAlertWithTitle:@"New User"
message:@"Enter your name:" viewStyle:UIAlertViewStylePlainTextInput message:@"Enter your name:" viewStyle:UIAlertViewStylePlainTextInput
initAlert:^(UIAlertView *alert, UITextField *firstField) { initAlert:^(UIAlertView *alert, UITextField *firstField) {
firstField.autocapitalizationType = UITextAutocapitalizationTypeWords; firstField.autocapitalizationType = UITextAutocapitalizationTypeWords;
firstField.autocorrectionType = UITextAutocorrectionTypeYes; firstField.autocorrectionType = UITextAutocorrectionTypeYes;
firstField.spellCheckingType = UITextSpellCheckingTypeYes; firstField.spellCheckingType = UITextSpellCheckingTypeYes;
firstField.keyboardType = UIKeyboardTypeAlphabet; firstField.keyboardType = UIKeyboardTypeAlphabet;
} }
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
newUserAvatar.selected = NO; newUserAvatar.selected = NO;
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:[MPAppDelegate managedObjectContext]]; inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newUser.name = [alert textFieldAtIndex:0].text; newUser.name = [alert textFieldAtIndex:0].text;
self.selectedUser = newUser; self.selectedUser = newUser;
[self updateUsers]; [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 { - (void)updateLayoutAnimated:(BOOL)animated allowScroll:(BOOL)allowScroll completion:(void (^)(BOOL finished))completion {
if (animated) { if (animated) {
self.oldNameLabel.text = self.nameLabel.text; self.oldNameLabel.text = self.nameLabel.text;
self.oldNameLabel.alpha = 1; self.oldNameLabel.alpha = 1;
self.nameLabel.alpha = 0; self.nameLabel.alpha = 0;
[UIView animateWithDuration:0.5f animations:^{ [UIView animateWithDuration:0.5f animations:^{
[self updateLayoutAnimated:NO allowScroll:allowScroll completion:nil]; [self updateLayoutAnimated:NO allowScroll:allowScroll completion:nil];
self.oldNameLabel.alpha = 0; self.oldNameLabel.alpha = 0;
self.nameLabel.alpha = 1; self.nameLabel.alpha = 1;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (completion) if (completion)
completion(finished); completion(finished);
}]; }];
return; 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; if (self.selectedUser && !self.passwordView.alpha) {
UIButton *selectedAvatar = [self avatarForUser:self.selectedUser]; self.passwordView.alpha = 1;
UIButton *targetedAvatar = selectedAvatar; 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) { if (!targetedAvatar) {
targetedAvatar = [self findTargetedAvatar]; targetedAvatar = [self findTargetedAvatar];
targetedUser = [self userForAvatar:targetedAvatar]; targetedUser = [self userForAvatar:targetedAvatar];
} }
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if (![[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) 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; return;
UIButton *avatar = (UIButton *)subview; UIButton *avatar = (UIButton *)subview;
BOOL isTargeted = avatar == targetedAvatar; BOOL isTargeted = avatar == targetedAvatar;
avatar.userInteractionEnabled = isTargeted; 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]; [self updateAvatarShadowColor:avatar isTargeted:isTargeted];
} recurse:NO]; } recurse:NO];
if (allowScroll) { if (allowScroll) {
CGPoint targetContentOffset = CGPointMake(MAX(0, targetedAvatar.center.x - self.avatarsView.bounds.size.width / 2), 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)) if (!CGPointEqualToPoint(self.avatarsView.contentOffset, targetContentOffset))
[self.avatarsView setContentOffset:targetContentOffset animated:animated]; [self.avatarsView setContentOffset:targetContentOffset animated:animated];
} }
self.nameLabel.text = targetedUser ? targetedUser.name : @"New User"; self.nameLabel.text = targetedUser? targetedUser.name: @"New User";
self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds, self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds,
[self.nameLabel.text sizeWithFont:self.nameLabel.font [self.nameLabel.text sizeWithFont:self.nameLabel.font
constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100) constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100)
lineBreakMode:self.nameLabel.lineBreakMode].height); lineBreakMode:self.nameLabel.lineBreakMode].height);
self.oldNameLabel.bounds = self.nameLabel.bounds; self.oldNameLabel.bounds = self.nameLabel.bounds;
if (completion) if (completion)
completion(YES); completion(YES);
@ -290,45 +293,47 @@
} }
- (void)tryMasterPassword { - (void)tryMasterPassword {
[self setSpinnerActive:YES]; [self setSpinnerActive:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text]; BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (unlocked) { 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]; [self dismissModalViewControllerAnimated:YES];
}); });
} else if (self.passwordField.text.length) } else
[self setPasswordTip:@"Incorrect password."]; if (self.passwordField.text.length)
[self setPasswordTip:@"Incorrect password."];
[self setSpinnerActive:NO]; [self setSpinnerActive:NO];
}); });
}); });
} }
- (UIButton *)findTargetedAvatar { - (UIButton *)findTargetedAvatar {
CGFloat xOfMiddle = self.avatarsView.contentOffset.x + self.avatarsView.bounds.size.width / 2; 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 { - (UIButton *)avatarForUser:(MPUserEntity *)user {
__block UIButton *avatar = nil; __block UIButton *avatar = nil;
if (user) if (user)
[self.avatarToUser enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [self.avatarToUser enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (obj == user) if (obj == user)
avatar = [key nonretainedObjectValue]; avatar = [key nonretainedObjectValue];
}]; }];
return avatar; return avatar;
} }
- (MPUserEntity *)userForAvatar:(UIButton *)avatar { - (MPUserEntity *)userForAvatar:(UIButton *)avatar {
return NSNullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]); return NSNullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]);
} }
@ -336,16 +341,16 @@
PearlMainThread(^{ PearlMainThread(^{
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.toValue = [NSNumber numberWithDouble:2 * M_PI]; rotate.toValue = [NSNumber numberWithDouble:2 * M_PI];
rotate.duration = 5.0; rotate.duration = 5.0;
if (active) { if (active) {
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
rotate.fromValue = [NSNumber numberWithFloat:0]; rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.repeatCount = MAXFLOAT; rotate.repeatCount = MAXFLOAT;
} else { } else {
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
rotate.repeatCount = 1; rotate.repeatCount = 1;
} }
[self.spinner.layer removeAnimationForKey:@"rotation"]; [self.spinner.layer removeAnimationForKey:@"rotation"];
@ -363,48 +368,49 @@
} }
- (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted { - (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted {
if (targeted) { if (targeted) {
if (![avatar.layer animationForKey:@"targetedShadow"]) { if (![avatar.layer animationForKey:@"targetedShadow"]) {
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"]; 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.beginTime = 0.0f;
toShadowColorAnimation.duration = 0.5f; toShadowColorAnimation.duration = 0.5f;
toShadowColorAnimation.fillMode = kCAFillModeForwards; toShadowColorAnimation.fillMode = kCAFillModeForwards;
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
toShadowOpacityAnimation.toValue = PearlFloat(0.2); toShadowOpacityAnimation.toValue = PearlFloat(0.2);
toShadowOpacityAnimation.duration = 0.5f; toShadowOpacityAnimation.duration = 0.5f;
CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
pulseShadowOpacityAnimation.fromValue = PearlFloat(0.2); pulseShadowOpacityAnimation.fromValue = PearlFloat(0.2);
pulseShadowOpacityAnimation.toValue = PearlFloat(0.6); pulseShadowOpacityAnimation.toValue = PearlFloat(0.6);
pulseShadowOpacityAnimation.beginTime = 0.5f; pulseShadowOpacityAnimation.beginTime = 0.5f;
pulseShadowOpacityAnimation.duration = 2.0f; pulseShadowOpacityAnimation.duration = 2.0f;
pulseShadowOpacityAnimation.autoreverses = YES; pulseShadowOpacityAnimation.autoreverses = YES;
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT; pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation, nil]; group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation, nil];
group.duration = MAXFLOAT; group.duration = MAXFLOAT;
[avatar.layer removeAnimationForKey:@"inactiveShadow"]; [avatar.layer removeAnimationForKey:@"inactiveShadow"];
[avatar.layer addAnimation:group forKey:@"targetedShadow"]; [avatar.layer addAnimation:group forKey:@"targetedShadow"];
} }
} else { } else {
if ([avatar.layer animationForKey:@"targetedShadow"]) { if ([avatar.layer animationForKey:@"targetedShadow"]) {
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"]; CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
toShadowColorAnimation.toValue = (__bridge id) [UIColor blackColor].CGColor; toShadowColorAnimation.toValue = (__bridge id)[UIColor blackColor].CGColor;
toShadowColorAnimation.duration = 0.5f; toShadowColorAnimation.duration = 0.5f;
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
toShadowOpacityAnimation.toValue = PearlFloat(1); toShadowOpacityAnimation.toValue = PearlFloat(1);
toShadowOpacityAnimation.duration = 0.5f; toShadowOpacityAnimation.duration = 0.5f;
CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, nil]; group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, nil];
group.duration = 0.5f; group.duration = 0.5f;
[avatar.layer removeAnimationForKey:@"targetedShadow"]; [avatar.layer removeAnimationForKey:@"targetedShadow"];
[avatar.layer addAnimation:group forKey:@"inactiveShadow"]; [avatar.layer addAnimation:group forKey:@"inactiveShadow"];
} }
@ -419,91 +425,95 @@
} }
- (BOOL)textFieldShouldReturn:(UITextField *)textField { - (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder]; [textField resignFirstResponder];
[self setSpinnerActive:YES]; [self setSpinnerActive:YES];
if (self.selectedUser.keyID) if (self.selectedUser.keyID)
[self tryMasterPassword]; [self tryMasterPassword];
else else
[PearlAlert showAlertWithTitle:@"New Master Password" [PearlAlert showAlertWithTitle:@"New Master Password"
message:@"Please confirm the spelling of this new master password." message:@"Please confirm the spelling of this new master password."
viewStyle:UIAlertViewStyleSecureTextInput viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[self setSpinnerActive:NO]; [self setSpinnerActive:NO];
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) { if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) {
[PearlAlert showAlertWithTitle:@"Incorrect Master Password" [PearlAlert showAlertWithTitle:@"Incorrect Master Password"
message: message:
@"The password you entered doesn't match with the master password you tried to use. " @"The password you entered doesn't match with the master password you tried to use. "
@"You've probably mistyped one of them.\n\n" @"You've probably mistyped one of them.\n\n"
@"Give it another try." @"Give it another try."
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil]; viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
return; cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
} return;
}
[self tryMasterPassword];
} [self tryMasterPassword];
cancelTitle:[PearlStrings get].commonButtonCancel }
otherTitles:[PearlStrings get].commonButtonContinue, nil]; cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:[PearlStrings get].commonButtonContinue, nil];
return YES; return YES;
} }
#pragma mark - UIScrollViewDelegate #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; 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); *targetContentOffset = CGPointMake(middleAvatar.center.x - scrollView.bounds.size.width / 2, targetContentOffset->y);
[self updateLayoutAnimated:NO allowScroll:NO completion:nil]; [self updateLayoutAnimated:NO allowScroll:NO completion:nil];
// [self scrollToAvatar:middleAvatar animated:YES]; // [self scrollToAvatar:middleAvatar animated:YES];
} }
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self updateLayoutAnimated:YES allowScroll:YES completion:nil]; [self updateLayoutAnimated:YES allowScroll:YES completion:nil];
// [self scrollToAvatar:middleAvatar animated:YES]; // [self scrollToAvatar:middleAvatar animated:YES];
} }
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self updateLayoutAnimated:NO allowScroll:NO completion:nil]; [self updateLayoutAnimated:NO allowScroll:NO completion:nil];
} }
#pragma mark - IBActions #pragma mark - IBActions
- (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender { - (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) if (sender.state != UIGestureRecognizerStateBegan)
return; return;
if (self.selectedUser) if (self.selectedUser)
return; return;
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]]; MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]];
if (!targetedUser) if (!targetedUser)
return; return;
[PearlAlert showAlertWithTitle:@"Delete User" message: [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 viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser]; [[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
[[MPAppDelegate get] saveContext]; [[MPAppDelegate get] saveContext];
[self updateUsers]; [self updateUsers];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Delete", nil]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Delete", nil];
} }
@end @end

View File

@ -6,27 +6,25 @@
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPiOSConfig.h"
@implementation MPiOSConfig @implementation MPiOSConfig
@dynamic helpHidden, showQuickStart; @dynamic helpHidden, showQuickStart;
- (id)init { - (id)init {
if(!(self = [super init])) if (!(self = [super init]))
return self; return self;
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: [self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)), [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
@"510296984", NSStringFromSelector(@selector(iTunesID)), @"510296984", NSStringFromSelector(@selector(iTunesID)),
nil]]; nil]];
return self; return self;
} }
+ (MPiOSConfig *)get { + (MPiOSConfig *)get {
return (MPiOSConfig *)[super get]; return (MPiOSConfig *)[super get];
} }

View File

@ -11,12 +11,14 @@
#import "Pearl-Prefix.pch" #import "Pearl-Prefix.pch"
#ifdef __OBJC__ #ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "TestFlight.h" #import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "TestFlight.h"
#import "MPTypes.h"
#import "MPiOSConfig.h"
#import "MPTypes.h"
#import "MPiOSConfig.h"
#endif #endif

View File

@ -8,8 +8,8 @@
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{
@autoreleasepool { @autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MPAppDelegate class])); return UIApplicationMain(argc, argv, nil, NSStringFromClass([MPAppDelegate class]));
} }