Crashlytics, Localytics, TestFlight + password change warnings.
[ADDED] Crashlytics, Localytics. [IMPROVED] Async TestFlight takeOff. [REMOVED] TestFlight token hidden. [FIXED] Warnings, mostly to do with sign conversions. [ADDED] Warning messages whenever site's password changes, allowing the user to cancel the operation. [ADDED] Make password counter resettable by holding down on the counter increment button.
This commit is contained in:
@@ -42,9 +42,7 @@ static NSDictionary *keyHashQuery() {
|
||||
[PearlKeyChain deleteItemForQuery:keyHashQuery()];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)signOut {
|
||||
@@ -63,9 +61,7 @@ static NSDictionary *keyHashQuery() {
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
dbg(@"Deleting key from key chain.");
|
||||
[PearlKeyChain deleteItemForQuery:keyQuery()];
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,15 +92,11 @@ static NSDictionary *keyHashQuery() {
|
||||
if (![keyHash isEqual:tryKeyHash]) {
|
||||
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyHash, tryKeyHash);
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||
#endif
|
||||
return NO;
|
||||
}
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
|
||||
#endif
|
||||
|
||||
[self updateKey:tryKey];
|
||||
return YES;
|
||||
@@ -142,9 +134,7 @@ static NSDictionary *keyHashQuery() {
|
||||
nil]];
|
||||
}
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSetKeyphraseLength, key.length]];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,6 @@
|
||||
|
||||
@interface MPElementGeneratedEntity : MPElementEntity
|
||||
|
||||
@property (nonatomic, assign) uint16_t counter;
|
||||
@property (nonatomic, assign) int16_t counter;
|
||||
|
||||
@end
|
||||
|
@@ -22,7 +22,7 @@
|
||||
return nil;
|
||||
|
||||
if (self.type & MPElementTypeClassCalculated)
|
||||
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter);
|
||||
return MPCalculateContent((unsigned)self.type, self.name, [MPAppDelegate get].key, self.counter);
|
||||
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:[NSString stringWithFormat:@"Unsupported type: %d", self.type] userInfo:nil];
|
||||
|
@@ -32,10 +32,10 @@ typedef enum {
|
||||
MPElementTypeStoredDevicePrivate = MPElementTypeClassStored | 0x02,
|
||||
} MPElementType;
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
|
||||
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
|
||||
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
|
||||
#define MPTestFlightCheckpointResetPasswordCounter @"MPTestFlightCheckpointResetPasswordCounter"
|
||||
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
|
||||
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
|
||||
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
|
||||
@@ -56,7 +56,6 @@ typedef enum {
|
||||
#define MPTestFlightCheckpointMPAsked @"MPTestFlightCheckpointMPAsked"
|
||||
#define MPTestFlightCheckpointStoreIncompatible @"MPTestFlightCheckpointStoreIncompatible"
|
||||
#define MPTestFlightCheckpointSetKeyphraseLength @"MPTestFlightCheckpointSetKeyphraseLength_%d"
|
||||
#endif
|
||||
|
||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
||||
#define MPNotificationKeySet @"MPNotificationKeySet"
|
||||
@@ -69,4 +68,4 @@ NSData *keyHashForKey(NSData *key);
|
||||
NSString *NSStringFromMPElementType(MPElementType type);
|
||||
NSString *ClassNameFromMPElementType(MPElementType type);
|
||||
Class ClassFromMPElementType(MPElementType type);
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint16_t counter);
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int16_t counter);
|
||||
|
@@ -102,7 +102,7 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
|
||||
}
|
||||
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint16_t counter) {
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int16_t counter) {
|
||||
|
||||
assert(type & MPElementTypeClassCalculated);
|
||||
|
||||
|
@@ -11,6 +11,23 @@
|
||||
|
||||
#import "MPMainViewController.h"
|
||||
#import "IASKSettingsReader.h"
|
||||
#import "LocalyticsSession.h"
|
||||
#import "TestFlight.h"
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
|
||||
@interface MPAppDelegate ()
|
||||
|
||||
- (NSString *)testFlightInfo;
|
||||
- (NSString *)testFlightToken;
|
||||
|
||||
- (NSString *)crashlyticsInfo;
|
||||
- (NSString *)crashlyticsAPIKey;
|
||||
|
||||
- (NSString *)localyticsInfo;
|
||||
- (NSString *)localyticsKey;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation MPAppDelegate
|
||||
|
||||
@@ -28,30 +45,70 @@
|
||||
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelDebug;
|
||||
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
|
||||
// [NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
@try {
|
||||
[TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"];
|
||||
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], @"logToConsole",
|
||||
[NSNumber numberWithBool:NO], @"logToSTDERR",
|
||||
nil]];
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelInfo)
|
||||
TFLog(@"%@", message);
|
||||
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err(@"TestFlight: %@", exception);
|
||||
}
|
||||
#ifndef DEBUG
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
@try {
|
||||
NSString *testFlightToken = [self testFlightToken];
|
||||
if ([testFlightToken length]) {
|
||||
dbg(@"Initializing TestFlight");
|
||||
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
|
||||
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], @"logToConsole",
|
||||
[NSNumber numberWithBool:NO], @"logToSTDERR",
|
||||
nil]];
|
||||
[TestFlight takeOff:testFlightToken];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelInfo)
|
||||
TFLog(@"%@", message);
|
||||
|
||||
return YES;
|
||||
}];
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err(@"TestFlight: %@", exception);
|
||||
}
|
||||
@try {
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
dbg(@"Initializing Crashlytics");
|
||||
//[Crashlytics sharedInstance].debugMode = YES;
|
||||
[Crashlytics startWithAPIKey:crashlyticsAPIKey afterDelay:0];
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err(@"Crashlytics: %@", exception);
|
||||
}
|
||||
@try {
|
||||
NSString *localyticsKey = [self localyticsKey];
|
||||
if ([localyticsKey length]) {
|
||||
dbg(@"Initializing Localytics");
|
||||
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelError)
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[message levelDescription],
|
||||
@"level",
|
||||
message.message,
|
||||
@"message",
|
||||
nil]];
|
||||
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err(@"Localytics exception: %@", exception);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
|
||||
@@ -59,10 +116,10 @@
|
||||
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UINavigationBar appearance] setTitleTextAttributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0], UITextAttributeFont,
|
||||
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
|
||||
nil]];
|
||||
|
||||
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
|
||||
@@ -73,10 +130,10 @@
|
||||
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UIBarButtonItem appearance] setTitleTextAttributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0], UITextAttributeFont,
|
||||
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
|
||||
nil]
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
@@ -118,7 +175,7 @@
|
||||
[self loadKey:YES];
|
||||
}];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
#ifdef ADHOC
|
||||
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
|
||||
@"Thank you for taking the time to test Master Password.\n\n"
|
||||
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
|
||||
@@ -143,18 +200,14 @@
|
||||
else
|
||||
[self loadKey:NO];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)showGuide {
|
||||
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)loadKey:(BOOL)animated {
|
||||
@@ -172,6 +225,34 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
|
||||
[super applicationDidEnterBackground:application];
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] resume];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
|
||||
[super applicationWillEnterForeground:application];
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
|
||||
[self saveContext];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
|
||||
[super applicationWillTerminate:application];
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
|
||||
[self saveContext];
|
||||
@@ -179,18 +260,7 @@
|
||||
if (![[MPiOSConfig get].rememberKey boolValue])
|
||||
[self updateKey:nil];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
|
||||
[self saveContext];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext {
|
||||
@@ -279,9 +349,7 @@
|
||||
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
|
||||
#endif
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointStoreIncompatible];
|
||||
#endif
|
||||
|
||||
@throw [NSException exceptionWithName:error.domain reason:error.localizedDescription
|
||||
userInfo:[NSDictionary dictionaryWithObject:error forKey:@"cause"]];
|
||||
@@ -306,4 +374,70 @@
|
||||
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - TestFlight
|
||||
|
||||
|
||||
static NSDictionary *testFlightInfo = nil;
|
||||
|
||||
- (NSDictionary *)testFlightInfo {
|
||||
|
||||
if (testFlightInfo == nil)
|
||||
testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
|
||||
|
||||
return testFlightInfo;
|
||||
}
|
||||
|
||||
- (NSString *)testFlightToken {
|
||||
|
||||
return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Crashlytics
|
||||
|
||||
|
||||
static NSDictionary *crashlyticsInfo = nil;
|
||||
|
||||
- (NSDictionary *)crashlyticsInfo {
|
||||
|
||||
if (crashlyticsInfo == nil)
|
||||
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
|
||||
|
||||
return crashlyticsInfo;
|
||||
}
|
||||
|
||||
- (NSString *)crashlyticsAPIKey {
|
||||
|
||||
return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Localytics
|
||||
|
||||
|
||||
static NSDictionary *localyticsInfo = nil;
|
||||
|
||||
- (NSDictionary *)localyticsInfo {
|
||||
|
||||
if (localyticsInfo == nil)
|
||||
localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
|
||||
|
||||
return localyticsInfo;
|
||||
}
|
||||
|
||||
- (NSString *)localyticsKey {
|
||||
|
||||
#ifdef DEBUG
|
||||
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
|
||||
#elif defined(LITE)
|
||||
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution.lite"]);
|
||||
#else
|
||||
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -34,6 +34,7 @@
|
||||
|
||||
- (IBAction)copyContent;
|
||||
- (IBAction)incrementPasswordCounter;
|
||||
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender;
|
||||
- (IBAction)editPassword;
|
||||
- (IBAction)closeAlert;
|
||||
- (IBAction)action:(UIBarButtonItem *)sender;
|
||||
|
@@ -21,7 +21,8 @@
|
||||
- (void)updateWasAnimated:(BOOL)animated;
|
||||
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
|
||||
- (void)updateElement:(void (^)(void))updateElement;
|
||||
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task;
|
||||
- (void)changeElementWithoutWarningDo:(void (^)(void))task;
|
||||
|
||||
@end
|
||||
|
||||
@@ -163,9 +164,9 @@
|
||||
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassCalculated? 0.5f: 0;
|
||||
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
|
||||
|
||||
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
|
||||
[self.typeButton setTitle:NSStringFromMPElementType((unsigned)self.activeElement.type)
|
||||
forState:UIControlStateNormal];
|
||||
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
|
||||
self.typeButton.alpha = NSStringFromMPElementType((unsigned)self.activeElement.type).length? 1: 0;
|
||||
|
||||
self.contentField.enabled = NO;
|
||||
|
||||
@@ -212,9 +213,7 @@
|
||||
|
||||
- (void)setHelpChapter:(NSString *)chapter {
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]];
|
||||
#endif
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.helpView loadRequest:
|
||||
@@ -227,7 +226,7 @@
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
||||
|
||||
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
|
||||
ClassNameFromMPElementType(self.activeElement.type)]];
|
||||
ClassNameFromMPElementType((unsigned)self.activeElement.type)]];
|
||||
if (error.length)
|
||||
err(@"helpView.setClass: %@", error);
|
||||
}
|
||||
@@ -285,42 +284,76 @@
|
||||
|
||||
[self showContentTip:@"Copied!" withIcon:nil];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (IBAction)incrementPasswordCounter {
|
||||
|
||||
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
// Not of a type that supports a password counter;
|
||||
// Not of a type that supports a password counter.
|
||||
return;
|
||||
|
||||
[self updateElement:^{
|
||||
++((MPElementGeneratedEntity *) self.activeElement).counter;
|
||||
}];
|
||||
[self changeElementWithWarning:
|
||||
@"You are incrementing the site's password counter.\n\n"
|
||||
@"If you continue, a new password will be generated for this site. "
|
||||
@"You will then need to update your account's old password to this newly generated password.\n"
|
||||
@"You can reset the counter by holding down on this button."
|
||||
do:^{
|
||||
++((MPElementGeneratedEntity *) self.activeElement).counter;
|
||||
}];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)updateElement:(void (^)(void))updateElement {
|
||||
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender {
|
||||
|
||||
// Update password counter.
|
||||
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
// Not of a type that supports a password counter.
|
||||
return;
|
||||
if (((MPElementGeneratedEntity *)self.activeElement).counter == 1)
|
||||
// Counter has initial value, no point resetting.
|
||||
return;
|
||||
|
||||
[self changeElementWithWarning:
|
||||
@"You are resetting the site's password counter.\n\n"
|
||||
@"If you continue, the site's password will change back to its original value. "
|
||||
@"You will then need to update your account's password back to this original value."
|
||||
do:^{
|
||||
((MPElementGeneratedEntity *) self.activeElement).counter = 1;
|
||||
}];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter];
|
||||
}
|
||||
|
||||
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[self changeElementWithoutWarningDo:task];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
- (void)changeElementWithoutWarningDo:(void (^)(void))task; {
|
||||
|
||||
// Update element, keeping track of the old password.
|
||||
NSString *oldPassword = self.activeElement.description;
|
||||
updateElement();
|
||||
task();
|
||||
NSString *newPassword = self.activeElement.description;
|
||||
[self updateAnimated:YES];
|
||||
|
||||
// Show new and old password.
|
||||
if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
|
||||
[self showAlertWithTitle:@"Password Changed!" message:l(@"The password for %@ has changed.\n\n"
|
||||
@"IMPORTANT:\n"
|
||||
@"Don't forget to update the site with your new password! "
|
||||
@"Your old password was:\n"
|
||||
@"%@", self.activeElement.name, oldPassword)];
|
||||
}
|
||||
|
||||
|
||||
- (IBAction)editPassword {
|
||||
|
||||
if (self.activeElement.type & MPElementTypeClassStored) {
|
||||
@@ -328,9 +361,7 @@
|
||||
[self.contentField becomeFirstResponder];
|
||||
}
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (IBAction)closeAlert {
|
||||
@@ -342,9 +373,7 @@
|
||||
self.alertBody.text = nil;
|
||||
}];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (IBAction)action:(id)sender {
|
||||
@@ -371,7 +400,7 @@
|
||||
[self.navigationController pushViewController:settingsVC animated:YES];
|
||||
break;
|
||||
}
|
||||
#ifdef TESTFLIGHT
|
||||
#ifdef ADHOC
|
||||
case 4:
|
||||
[TestFlight openFeedbackView];
|
||||
break;
|
||||
@@ -386,13 +415,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointAction];
|
||||
#endif
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil
|
||||
otherTitles:
|
||||
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings",
|
||||
#ifdef TESTFLIGHT
|
||||
#ifdef ADHOC
|
||||
@"Feedback",
|
||||
#endif
|
||||
@"Sign Out",
|
||||
@@ -401,36 +428,38 @@
|
||||
|
||||
- (MPElementType)selectedType {
|
||||
|
||||
return self.activeElement.type;
|
||||
return (unsigned)self.activeElement.type;
|
||||
}
|
||||
|
||||
- (void)didSelectType:(MPElementType)type {
|
||||
|
||||
[self updateElement:^{
|
||||
// Update password type.
|
||||
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
|
||||
// Type requires a different class of element. Recreate the element.
|
||||
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
newElement.name = self.activeElement.name;
|
||||
newElement.mpHashHex = self.activeElement.mpHashHex;
|
||||
newElement.uses = self.activeElement.uses;
|
||||
newElement.lastUsed = self.activeElement.lastUsed;
|
||||
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
|
||||
self.activeElement = newElement;
|
||||
}];
|
||||
|
||||
self.activeElement.type = type;
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
|
||||
#endif
|
||||
|
||||
if (type & MPElementTypeClassStored && ![self.activeElement.description length])
|
||||
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
|
||||
}];
|
||||
[self changeElementWithWarning:
|
||||
@"You are about to change the type of this password.\n\n"
|
||||
@"If you continue, the password for this site will change. "
|
||||
@"You will need to update your account's old password to the new one."
|
||||
do:^{
|
||||
// Update password type.
|
||||
if (ClassFromMPElementType(type) != ClassFromMPElementType((unsigned)self.activeElement.type))
|
||||
// Type requires a different class of element. Recreate the element.
|
||||
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
newElement.name = self.activeElement.name;
|
||||
newElement.mpHashHex = self.activeElement.mpHashHex;
|
||||
newElement.uses = self.activeElement.uses;
|
||||
newElement.lastUsed = self.activeElement.lastUsed;
|
||||
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
|
||||
self.activeElement = newElement;
|
||||
}];
|
||||
|
||||
self.activeElement.type = type;
|
||||
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
|
||||
|
||||
if (type & MPElementTypeClassStored && ![self.activeElement.description length])
|
||||
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didSelectElement:(MPElementEntity *)element {
|
||||
@@ -443,17 +472,14 @@
|
||||
[self showAlertWithTitle:@"New Site" message:
|
||||
l(@"You've just created a password for %@.\n\n"
|
||||
@"IMPORTANT:\n"
|
||||
@"Don't forget to set or change the password for your account at %@ to the password above. "
|
||||
@"It's best to do this right away. If you forget it, may get confusing later on "
|
||||
@"to remember what password you need to use for logging into the site.",
|
||||
@"Go to %@ and set or change the password for your account to the password above.\n"
|
||||
@"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.",
|
||||
self.activeElement.name, self.activeElement.name)];
|
||||
|
||||
[self.searchDisplayController setActive:NO animated:YES];
|
||||
self.searchDisplayController.searchBar.text = self.activeElement.name;
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement];
|
||||
#endif
|
||||
}
|
||||
|
||||
[self updateAnimated:YES];
|
||||
@@ -479,7 +505,7 @@
|
||||
// Content hasn't changed.
|
||||
return;
|
||||
|
||||
[self updateElement:^{
|
||||
[self changeElementWithoutWarningDo:^{
|
||||
((MPElementStoredEntity *) self.activeElement).content = self.contentField.text;
|
||||
}];
|
||||
}
|
||||
@@ -489,9 +515,7 @@
|
||||
navigationType:(UIWebViewNavigationType)navigationType {
|
||||
|
||||
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink];
|
||||
#endif
|
||||
|
||||
[[UIApplication sharedApplication] openURL:[request URL]];
|
||||
return NO;
|
||||
|
@@ -41,8 +41,8 @@
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
|
||||
for (NSUInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
|
||||
NSUInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
|
||||
for (NSInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
|
||||
NSInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
|
||||
if (!rowCount)
|
||||
continue;
|
||||
|
||||
@@ -54,9 +54,7 @@
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
|
||||
#endif
|
||||
|
||||
[self.delegate didSelectElement:nil];
|
||||
}
|
||||
@@ -189,13 +187,13 @@
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
|
||||
return [[self.fetchedResultsController sections] count] + ([self.query length]? 1: 0);
|
||||
return (signed)[[self.fetchedResultsController sections] count] + ([self.query length]? 1: 0);
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
|
||||
if (section < [[self.fetchedResultsController sections] count])
|
||||
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
|
||||
if (section < (signed)[[self.fetchedResultsController sections] count])
|
||||
return (signed)[[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] numberOfObjects];
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -230,7 +228,7 @@
|
||||
|
||||
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < [[self.fetchedResultsController sections] count]) {
|
||||
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) {
|
||||
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
|
||||
|
||||
cell.textLabel.text = element.name;
|
||||
@@ -245,7 +243,7 @@
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < [[self.fetchedResultsController sections] count])
|
||||
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count])
|
||||
[self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]];
|
||||
|
||||
else {
|
||||
@@ -263,7 +261,7 @@
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
|
||||
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
|
||||
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
|
||||
assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]);
|
||||
assert([MPAppDelegate get].keyHashHex);
|
||||
|
||||
element.name = siteName;
|
||||
@@ -279,8 +277,8 @@
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
|
||||
|
||||
if (section < [[self.fetchedResultsController sections] count])
|
||||
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
|
||||
if (section < (signed)[[self.fetchedResultsController sections] count])
|
||||
return [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name];
|
||||
|
||||
return @"";
|
||||
}
|
||||
@@ -297,15 +295,13 @@
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < [[self.fetchedResultsController sections] count]) {
|
||||
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) {
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete)
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
|
||||
[self.fetchedResultsController.managedObjectContext deleteObject:element];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement];
|
||||
#endif
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
@@ -173,7 +173,7 @@ typedef enum {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self showMessage:@"Success!" state:MPLockscreenSuccess];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 1.5f), dispatch_get_main_queue(), ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{
|
||||
[self dismissModalViewControllerAnimated:YES];
|
||||
});
|
||||
});
|
||||
@@ -214,9 +214,7 @@ typedef enum {
|
||||
|
||||
[[MPAppDelegate get] loadKey:YES];
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
|
||||
#endif
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
|
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2177" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2182" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<dependencies>
|
||||
<deployment defaultVersion="1296" identifier="iOS"/>
|
||||
<development defaultVersion="4200" identifier="xcode"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1173"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1181"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Type View Controller - Type-->
|
||||
@@ -399,6 +400,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
||||
<button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt">
|
||||
<rect key="frame" x="272.5" y="18.5" width="36.5" height="36"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<gestureRecognizers/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
|
||||
<state key="normal" image="icon_plus.png">
|
||||
@@ -410,6 +412,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="incrementPasswordCounter" destination="PQa-Xl-A3x" eventType="touchUpInside" id="hMc-kb-yFA"/>
|
||||
<outletCollection property="gestureRecognizers" destination="cZr-Fj-eBw" appends="YES" id="azb-m1-tta"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9FS-fS-xH6">
|
||||
@@ -593,6 +596,12 @@ L4m3P4sSw0rD</string>
|
||||
<outlet property="typeButton" destination="Cei-5z-uWE" id="4M1-d7-5Bh"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="cZr-Fj-eBw">
|
||||
<connections>
|
||||
<action selector="resetPasswordCounter:" destination="PQa-Xl-A3x" id="JL9-Ob-AjQ"/>
|
||||
<outlet property="delegate" destination="PQa-Xl-A3x" id="PMQ-so-Cj7"/>
|
||||
</connections>
|
||||
</pongPressGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="455" y="182"/>
|
||||
</scene>
|
||||
@@ -816,4 +825,4 @@ L4m3P4sSw0rD</string>
|
||||
<simulatedOrientationMetrics key="orientation"/>
|
||||
<simulatedScreenMetrics key="destination"/>
|
||||
</simulatedMetricsContainer>
|
||||
</document>
|
||||
</document>
|
@@ -15,9 +15,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
#import "TestFlight.h"
|
||||
#endif
|
||||
#import "TestFlight.h"
|
||||
|
||||
#import "MPTypes.h"
|
||||
#import "MPiOSConfig.h"
|
||||
|
Reference in New Issue
Block a user