AppCode code formatting.
This commit is contained in:
parent
d59f77720c
commit
a8bf74a925
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
|||||||
Subproject commit e55ef6876ee26f61a7cd2c075fc1e7a942016de0
|
Subproject commit 009482a08a2a05e9856c2158c1040d01aeedb5ff
|
@ -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];
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPElementEntity.h"
|
#import "MPElementEntity.h"
|
||||||
#import "MPUserEntity.h"
|
|
||||||
|
|
||||||
|
|
||||||
@implementation MPElementEntity
|
@implementation MPElementEntity
|
||||||
|
@ -13,6 +13,6 @@
|
|||||||
|
|
||||||
@interface MPElementGeneratedEntity : MPElementEntity
|
@interface MPElementGeneratedEntity : MPElementEntity
|
||||||
|
|
||||||
@property (nonatomic, retain) NSNumber * counter_;
|
@property (nonatomic, retain) NSNumber *counter_;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -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
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPUserEntity.h"
|
#import "MPUserEntity.h"
|
||||||
#import "MPElementEntity.h"
|
|
||||||
|
|
||||||
|
|
||||||
@implementation MPUserEntity
|
@implementation MPUserEntity
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
@interface MPPasswordWindowController : NSWindowController <NSTextFieldDelegate> {
|
@interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate> {
|
||||||
|
|
||||||
NSString *_content;
|
NSString *_content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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]));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user