2
0

Improved feedback + logging.

[REMOVED]   Apptentive is now implemented by a standard iOS mail
            composer window and can optionally include logs.
[IMPROVED]  Better inf-level logging of what's going on.
[AUDITED]   Made sure no personal is going out through inf+ levels.
This commit is contained in:
Maarten Billemont
2012-06-14 21:56:54 +02:00
parent bc2da6a99b
commit a67d9676ba
21 changed files with 469 additions and 453 deletions

View File

@@ -9,7 +9,6 @@
#import <Crashlytics/Crashlytics.h>
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "ATConnect.h"
#import "LocalyticsSession.h"
@implementation MPAppDelegate_Shared (Key)
@@ -28,11 +27,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (key)
inf(@"Found key (for: %@) in keychain.", user.name);
inf(@"Found key in keychain for: %@", user.userID);
else {
user.saveKey = NO;
inf(@"No key found (for: %@) in keychain.", user.name);
inf(@"No key found in keychain for: %@", user.userID);
}
return key;
@@ -44,7 +43,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (![existingKey isEqualToData:self.key]) {
inf(@"Updating key in keychain.");
inf(@"Saving key in keychain for: %@", user.userID);
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData,
@@ -63,7 +63,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
user.saveKey = NO;
if (result == noErr) {
inf(@"Removed key (for: %@) from keychain.", user.name);
inf(@"Removed key from keychain for: %@", user.userID);
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT_SDK_VERSION
@@ -105,14 +105,10 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if ((tryKey = [self loadSavedKeyFor:user]))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
inf(@"Saved password doesn't match keyID for: %@", user.userID);
tryKey = nil;
[self forgetSavedKeyFor:user];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed
attributes:nil];
}
}
@@ -121,19 +117,24 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if ([password length])
if ((tryKey = keyForPassword(password, user.name)))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
tryKey = nil;
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed
attributes:nil];
tryKey = nil;
}
}
// No more methods left, fail if key still not known.
if (!tryKey)
if (!tryKey) {
inf(@"Login failed for: %@", user.userID);
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed attributes:nil];
return NO;
}
inf(@"Logged in: %@", user.userID);
if (![self.key isEqualToData:tryKey]) {
self.key = tryKey;
@@ -141,10 +142,9 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
}
@try {
if ([[MPiOSConfig get].sendDebugInfo boolValue]) {
[TestFlight addCustomEnvironmentInformation:user.name forKey:@"username"];
[[Crashlytics sharedInstance] setObjectValue:user.name forKey:@"username"];
[[ATConnect sharedConnection] addAdditionalInfoToFeedback:user.name withKey:@"username"];
if ([[MPiOSConfig get].sendInfo boolValue]) {
[TestFlight addCustomEnvironmentInformation:user.userID forKey:@"username"];
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
}
}
@catch (id exception) {

View File

@@ -11,8 +11,6 @@
@implementation MPAppDelegate_Shared (Store)
static NSDateFormatter *rfc3339DateFormatter = nil;
#pragma mark - Core Data setup
+ (NSManagedObjectContext *)managedObjectContext {
@@ -136,26 +134,27 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
- (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' MPAppDelegate tampers with didSwitch a bit)
iCloudEnabled = manager.iCloudEnabled;
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled
attributes:nil];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause
context:(id)context {
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:PearlString(@"MPCheckpointMPErrorUbiquity_%d", cause)];
#endif
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
switch (cause) {
case UbiquityStoreManagerErrorCauseDeleteStore:
@@ -164,12 +163,13 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
case UbiquityStoreManagerErrorCauseClearStore:
break;
case UbiquityStoreManagerErrorCauseOpenLocalStore: {
wrn(@"Local store could not be opened, resetting it.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible
attributes:nil];
wrn(@"Local store could not be opened, resetting it.");
manager.hardResetEnabled = YES;
[manager hardResetLocalStorage];
@@ -177,12 +177,13 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
return;
}
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
wrn(@"iCloud store could not be opened, resetting it.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible
attributes:nil];
wrn(@"iCloud store could not be opened, resetting it.");
manager.hardResetEnabled = YES;
[manager hardResetCloudStorage];
break;
@@ -192,22 +193,10 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#pragma mark - Import / Export
- (void)loadRFC3339DateFormatter {
if (rfc3339DateFormatter)
return;
rfc3339DateFormatter = [NSDateFormatter new];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
}
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
[self loadRFC3339DateFormatter];
inf(@"Importing sites.");
static NSRegularExpression *headerPattern, *sitePattern;
__autoreleasing NSError *error;
@@ -299,17 +288,24 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"Couldn't search existing sites: %@", error);
if (!existingSites)
if (!existingSites) {
err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID);
return MPImportResultInternalError;
}
[elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
}
}
inf(@"Importing %u sites, deleting %u sites, for user: %@",
[importedSiteElements count], [elementsToDelete count], [MPUserEntity idFor:userName]);
// Ask for confirmation to import these sites.
if (!confirmation([importedSiteElements count], [elementsToDelete count]))
if (!confirmation([importedSiteElements count], [elementsToDelete count])) {
inf(@"Import cancelled.");
return MPImportResultCancelled;
}
// Delete existing sites.
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
@@ -326,7 +322,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
user.keyID = [keyIDHex decodeHex];
}
for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]];
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
NSString *name = [siteElements objectAtIndex:3];
@@ -345,6 +341,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
}
[self saveContext];
inf(@"Import completed successfully.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesImported];
#endif
@@ -356,7 +353,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
[self loadRFC3339DateFormatter];
inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", self.activeUser.userID);
// Header.
NSMutableString *export = [NSMutableString new];
@@ -370,7 +367,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# User Name: %@\n", self.activeUser.name];
[export appendFormat:@"# Key ID: %@\n", [self.activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
if (showPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"];
else
@@ -398,15 +395,14 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
}
[export appendFormat:@"%@ %8d %8d %20s\t%@\n",
[rfc3339DateFormatter stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
? content: @""];
}
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesExported];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesExported
attributes:nil];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesExported attributes:nil];
return export;
}

View File

@@ -36,5 +36,8 @@
@property (assign) NSUInteger avatar;
@property (assign) BOOL saveKey;
@property (assign) MPElementType defaultType;
@property (readonly) NSString *userID;
+ (NSString *)idFor:(NSString *)userName;
@end

View File

@@ -178,9 +178,20 @@
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
}
- (NSString *)userID {
return [MPUserEntity idFor:self.name];
}
- (void)setDefaultType:(MPElementType)aDefaultType {
self.defaultType_ = PearlUnsignedInteger(aDefaultType);
}
+ (NSString *)idFor:(NSString *)userName {
return [[userName hashWith:PearlHashSHA1] encodeHex];
}
@end

View File

@@ -64,6 +64,8 @@ typedef enum {
#define MPCheckpointCloudStoreIncompatible @"MPCheckpointCloudStoreIncompatible"
#define MPCheckpointSignInFailed @"MPCheckpointSignInFailed"
#define MPCheckpointSignedIn @"MPCheckpointSignedIn"
#define MPCheckpointConfig @"MPCheckpointConfig"
#define MPCheckpointCloud @"MPCheckpointCloud"
#define MPCheckpointCloudEnabled @"MPCheckpointCloudEnabled"
#define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
#define MPCheckpointSitesImported @"MPCheckpointSitesImported"

View File

@@ -13,7 +13,6 @@
#import "IASKSettingsReader.h"
#import "LocalyticsSession.h"
#import "ATConnect.h"
@interface MPAppDelegate ()
@@ -23,9 +22,6 @@
- (NSDictionary *)crashlyticsInfo;
- (NSString *)crashlyticsAPIKey;
- (NSDictionary *)apptentiveInfo;
- (NSString *)apptentiveAPIKey;
- (NSDictionary *)localyticsInfo;
- (NSString *)localyticsKey;
@@ -49,214 +45,85 @@
return (MPAppDelegate *)[super get];
}
- (void)checkConfig {
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
if ([[MPiOSConfig get].sendDebugInfo boolValue]) {
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloud boolValue] forKey:@"iCloud"];
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloudDecided boolValue] forKey:@"iCloudDecided"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].sendDebugInfo boolValue] forKey:@"sendDebugInfo"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].helpHidden boolValue] forKey:@"helpHidden"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].showQuickStart boolValue] forKey:@"showQuickStart"];
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].firstRun boolValue] forKey:@"firstRun"];
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].launchCount intValue] forKey:@"launchCount"];
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].askForReviews boolValue] forKey:@"askForReviews"];
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].reviewAfterLaunches intValue] forKey:@"reviewAfterLaunches"];
[[Crashlytics sharedInstance] setObjectValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
}
}
- (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
[TestFlight passCheckpoint:MPCheckpointShowGuide];
}
- (void)export {
[PearlAlert showNotice:
@"This will export all your site names.\n\n"
@"You can open the export with a text editor to get an overview of all your sites.\n\n"
@"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes."
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
@"Would you like to make all your passwords visible in the export?\n\n"
@"A safe export will only include your stored passwords, in an encrypted manner, "
@"making the result safe from falling in the wrong hands.\n\n"
@"If all your passwords are shown and somebody else finds the export, "
@"they could gain access to all your sites!"
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0)
// Safe Export
[self exportShowPasswords:NO];
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
// Safe Export
[self exportShowPasswords:YES];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
} otherTitles:nil];
}
- (void)exportShowPasswords:(BOOL)showPasswords {
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
NSString *message;
if (showPasswords)
message = @"Export of my Master Password sites with passwords visible.\n\nREMINDER: Make sure nobody else sees this file!\n";
else
message = @"Backup of my Master Password sites.\n";
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"'Master Password sites ('yyyy'-'MM'-'DD').mpsites'"];
MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init];
[composer setMailComposeDelegate:self];
[composer setSubject:@"Master Password site export"];
[composer setMessageBody:message isHTML:NO];
[composer addAttachmentData:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
fileName:[exportDateFormatter stringFromDate:[NSDate date]]];
[self.window.rootViewController presentModalViewController:composer animated:YES];
}
- (void)changeMP {
[PearlAlert showAlertWithTitle:@"Changing Master Password"
message:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
self.activeUser.keyID = nil;
[self signOut];
[TestFlight passCheckpoint:MPCheckpointChangeMP];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP
attributes:nil];
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
#pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
[self checkConfig];
}
#pragma mark - UIApplicationDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//#ifndef DEBUG
@try {
NSString *testFlightToken = [self testFlightToken];
if ([testFlightToken length]) {
dbg(@"Initializing TestFlight");
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
#ifdef ADHOC
[TestFlight setDeviceIdentifier:[(id)[UIDevice currentDevice] uniqueIdentifier]];
#else
[TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
#endif
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], @"logToConsole",
[NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight takeOff:testFlightToken];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelInfo;
if ([[MPiOSConfig get].sendDebugInfo boolValue])
level = PearlLogLevelDebug;
if (message.level >= level)
TFLog(@"%@", message);
return YES;
}];
[TestFlight passCheckpoint:MPCheckpointLaunched];
}
}
@catch (id exception) {
err(@"TestFlight: %@", exception);
}
@try {
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
if ([crashlyticsAPIKey length]) {
dbg(@"Initializing Crashlytics");
//[Crashlytics sharedInstance].debugMode = YES;
[[Crashlytics sharedInstance] setObjectValue:@"Anonymous" forKey:@"username"];
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
[Crashlytics startWithAPIKey:crashlyticsAPIKey afterDelay:0];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelInfo;
if ([[MPiOSConfig get].sendDebugInfo boolValue])
level = PearlLogLevelDebug;
if (message.level >= level)
CLSLog(@"%@", message);
return YES;
}];
}
}
@catch (id 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 (id exception) {
err(@"Localytics exception: %@", exception);
}
//#endif
});
@try {
NSString *apptentiveAPIKey = [self apptentiveAPIKey];
if ([apptentiveAPIKey length]) {
dbg(@"Initializing Apptentive");
NSString *testFlightToken = [self testFlightToken];
if ([testFlightToken length]) {
inf(@"Initializing TestFlight");
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
#ifdef ADHOC
[TestFlight setDeviceIdentifier:[(id)[UIDevice currentDevice] uniqueIdentifier]];
#else
[TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
#endif
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], @"logToConsole",
[NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight takeOff:testFlightToken];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPiOSConfig get].sendInfo boolValue])
level = PearlLogLevelInfo;
ATConnect *connection = [ATConnect sharedConnection];
[connection setApiKey:apptentiveAPIKey];
[connection setShouldTakeScreenshot:NO];
[connection addAdditionalInfoToFeedback:[PearlInfoPlist get].CFBundleVersion withKey:@"CFBundleVersion"];
[connection addAdditionalInfoToFeedback:[PearlKeyChain deviceIdentifier] withKey:@"deviceIdentifier"];
[connection addAdditionalInfoToFeedback:@"Anonymous" withKey:@"username"];
if (message.level >= level)
TFLog(@"%@", message);
return YES;
}];
}
}
@catch (NSException *exception) {
err(@"Apptentive: %@", exception);
@catch (id exception) {
err(@"TestFlight: %@", exception);
}
@try {
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
if ([crashlyticsAPIKey length]) {
inf(@"Initializing Crashlytics");
[Crashlytics sharedInstance].debugMode = YES;
[[Crashlytics sharedInstance] setObjectValue:@"Anonymous" forKey:@"username"];
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
[Crashlytics startWithAPIKey:crashlyticsAPIKey afterDelay:0];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPiOSConfig get].sendInfo boolValue])
level = PearlLogLevelInfo;
if (message.level >= level)
CLSLog(@"%@", message);
return YES;
}];
}
}
@catch (id exception) {
err(@"Crashlytics: %@", exception);
}
@try {
NSString *localyticsKey = [self localyticsKey];
if ([localyticsKey length]) {
inf(@"Initializing Localytics");
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelWarn)
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[message levelDescription],
@"level",
message.message,
@"message",
nil]];
return YES;
}];
}
}
@catch (id exception) {
err(@"Localytics exception: %@", exception);
}
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
@@ -290,26 +157,27 @@
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
/*
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
*/
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
usingBlock:^(NSNotification *note) {
@@ -334,7 +202,13 @@ UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
[super application:application didFinishLaunchingWithOptions:launchOptions];
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
[TestFlight passCheckpoint:MPCheckpointLaunched];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLaunched];
return YES;
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
@@ -407,6 +281,7 @@ UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.
- (void)applicationDidBecomeActive:(UIApplication *)application {
inf(@"Re-activated");
[[MPAppDelegate get] checkConfig];
if ([[MPiOSConfig get].showQuickStart boolValue])
@@ -447,6 +322,7 @@ UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.
- (void)applicationWillResignActive:(UIApplication *)application {
inf(@"Will deactivate");
[self saveContext];
if (![[MPiOSConfig get].rememberLogin boolValue])
@@ -455,6 +331,159 @@ UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.
[TestFlight passCheckpoint:MPCheckpointDeactivated];
}
#pragma - mark Behavior
- (void)checkConfig {
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
if ([[MPiOSConfig get].sendInfo boolValue]) {
if ([PearlLogger get].autoprintLevel > PearlLogLevelInfo)
[PearlLogger get].autoprintLevel = PearlLogLevelInfo;
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloud boolValue] forKey:@"iCloud"];
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloudDecided boolValue] forKey:@"iCloudDecided"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].sendInfo boolValue] forKey:@"sendInfo"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].helpHidden boolValue] forKey:@"helpHidden"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].showQuickStart boolValue] forKey:@"showQuickStart"];
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].firstRun boolValue] forKey:@"firstRun"];
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].launchCount intValue] forKey:@"launchCount"];
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].askForReviews boolValue] forKey:@"askForReviews"];
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].reviewAfterLaunches intValue] forKey:@"reviewAfterLaunches"];
[[Crashlytics sharedInstance] setObjectValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].rememberLogin boolValue]? @"YES": @"NO" forKey:@"rememberLogin"];
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloud boolValue]? @"YES": @"NO" forKey:@"iCloud"];
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloudDecided boolValue]? @"YES": @"NO" forKey:@"iCloudDecided"];
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].sendInfo boolValue]? @"YES": @"NO" forKey:@"sendInfo"];
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].helpHidden boolValue]? @"YES": @"NO" forKey:@"helpHidden"];
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].showQuickStart boolValue]? @"YES": @"NO" forKey:@"showQuickStart"];
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].firstRun boolValue]? @"YES": @"NO" forKey:@"firstRun"];
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].launchCount description] forKey:@"launchCount"];
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].askForReviews boolValue]? @"YES": @"NO" forKey:@"askForReviews"];
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].reviewAfterLaunches description] forKey:@"reviewAfterLaunches"];
[TestFlight addCustomEnvironmentInformation:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
[TestFlight passCheckpoint:MPCheckpointConfig];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointConfig attributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[[MPConfig get].rememberLogin boolValue]
? @"YES": @"NO", @"rememberLogin",
[[MPConfig get].iCloud boolValue]? @"YES"
: @"NO", @"iCloud",
[[MPConfig get].iCloudDecided boolValue]
? @"YES": @"NO", @"iCloudDecided",
[[MPiOSConfig get].sendInfo boolValue]
? @"YES": @"NO", @"sendInfo",
[[MPiOSConfig get].helpHidden boolValue]
? @"YES": @"NO", @"helpHidden",
[[MPiOSConfig get].showQuickStart boolValue]
? @"YES": @"NO", @"showQuickStart",
[[PearlConfig get].firstRun boolValue]
? @"YES": @"NO", @"firstRun",
[[PearlConfig get].launchCount description], @"launchCount",
[[PearlConfig get].askForReviews boolValue]
? @"YES": @"NO", @"askForReviews",
[[PearlConfig get].reviewAfterLaunches description], @"reviewAfterLaunches",
[PearlConfig get].reviewedVersion, @"reviewedVersion",
nil]];
}
}
- (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
[TestFlight passCheckpoint:MPCheckpointShowGuide];
}
- (void)export {
[PearlAlert showNotice:
@"This will export all your site names.\n\n"
@"You can open the export with a text editor to get an overview of all your sites.\n\n"
@"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes."
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
@"Would you like to make all your passwords visible in the export?\n\n"
@"A safe export will only include your stored passwords, in an encrypted manner, "
@"making the result safe from falling in the wrong hands.\n\n"
@"If all your passwords are shown and somebody else finds the export, "
@"they could gain access to all your sites!"
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0)
// Safe Export
[self exportShowPasswords:NO];
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
// Safe Export
[self exportShowPasswords:YES];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
} otherTitles:nil];
}
- (void)exportShowPasswords:(BOOL)showPasswords {
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
NSString *message;
if (showPasswords)
message = PearlString(
@"Export of %@'s Master Password sites with passwords visible.\n"
@"REMINDER: Make sure nobody else sees this file! All passwords are visible!\n",
self.activeUser.name);
else
message = PearlString(
@"Backup of %@'s Master Password sites.\n",
self.activeUser.name);
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"];
MFMailComposeViewController *composer = [MFMailComposeViewController new];
[composer setMailComposeDelegate:self];
[composer setSubject:@"Master Password Export"];
[composer setMessageBody:message isHTML:NO];
[composer addAttachmentData:
[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
fileName:PearlString(@"%@ (%@).mpsites",
self.activeUser.name,
[exportDateFormatter stringFromDate:[NSDate date]])];
[self.window.rootViewController presentModalViewController:composer animated:YES];
}
- (void)changeMP {
[PearlAlert showAlertWithTitle:@"Changing Master Password"
message:
@"If you continue, you'll be able to set a new master password.\n\n"
@"Changing your master password will cause all your generated passwords to change!\n"
@"Changing the master password back to the old one will cause your passwords to revert as well."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
inf(@"Unsetting master password for: %@.", self.activeUser.userID);
self.activeUser.keyID = nil;
[self forgetSavedKeyFor:self.activeUser];
[self signOut];
[TestFlight passCheckpoint:MPCheckpointChangeMP];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP
attributes:nil];
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
#pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
[self checkConfig];
}
#pragma mark - MFMailComposeViewControllerDelegate
- (void)mailComposeController:(MFMailComposeViewController *)controller
@@ -566,25 +595,6 @@ UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.
}
#pragma mark - Apptentive
- (NSDictionary *)apptentiveInfo {
static NSDictionary *apptentiveInfo = nil;
if (apptentiveInfo == nil)
apptentiveInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Apptentive" withExtension:@"plist"]];
return apptentiveInfo;
}
- (NSString *)apptentiveAPIKey {
return NSNullToNil([[self apptentiveInfo] valueForKeyPath:@"API Key"]);
}
#pragma mark - Localytics

View File

@@ -24,15 +24,22 @@
[self.scrollView autoSizeContent];
}
- (void)viewWillAppear:(BOOL)animated {
inf(@"Guide will appear.");
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Guide will disappear.");
[super viewWillDisappear:animated];
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];

View File

@@ -6,11 +6,12 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <MessageUI/MessageUI.h>
#import "MPTypeViewController.h"
#import "MPElementEntity.h"
#import "MPSearchDelegate.h"
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate>
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, MFMailComposeViewControllerDelegate>
@property (strong, nonatomic) MPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;

View File

@@ -10,7 +10,6 @@
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "ATConnect.h"
#import "LocalyticsSession.h"
@@ -66,8 +65,21 @@
((MPTypeViewController *)[segue destinationViewController]).delegate = self;
}
- (void)viewDidLoad {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize];
self.alertBody.text = nil;
self.contentTipEditIcon.hidden = YES;
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
inf(@"Main will appear.");
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
if (![MPAppDelegate get].activeUser)
@@ -83,7 +95,7 @@
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
[self updateAnimated:animated];
[super viewWillAppear:animated];
}
@@ -110,16 +122,10 @@
[super viewDidAppear:animated];
}
- (void)viewDidLoad {
- (void)viewWillDisappear:(BOOL)animated {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize];
self.alertBody.text = nil;
self.contentTipEditIcon.hidden = YES;
[super viewDidLoad];
inf(@"Main will disappear.");
[super viewWillDisappear:animated];
}
- (void)viewDidUnload {
@@ -280,6 +286,7 @@
if (!self.activeElement)
return;
inf(@"Copying password for: %@", self.activeElement.name);
[UIPasteboard generalPasteboard].string = [self.activeElement.content description];
[self showContentTip:@"Copied!" withIcon:nil];
@@ -303,14 +310,15 @@
@"You will then need to update your account's old password to this newly generated password.\n\n"
@"You can reset the counter by holding down on this button."
do:^{
inf(@"Incrementing password counter for: %@", self.activeElement.name);
++((MPElementGeneratedEntity *)self.activeElement).counter;
}];
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
}];
}
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender {
@@ -330,14 +338,15 @@
@"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:^{
inf(@"Resetting password counter for: %@", self.activeElement.name);
((MPElementGeneratedEntity *)self.activeElement).counter = 1;
}];
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
}];
}
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
@@ -376,13 +385,14 @@
if (self.activeElement.type & MPElementTypeClassStored) {
self.contentField.enabled = YES;
[self.contentField becomeFirstResponder];
}
[TestFlight passCheckpoint:MPCheckpointEditPassword];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
[TestFlight passCheckpoint:MPCheckpointEditPassword];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
nil]];
}
}
- (IBAction)closeAlert {
@@ -406,37 +416,88 @@
switch (buttonIndex - [sheet firstOtherButtonIndex]) {
case 0: {
inf(@"Action: Toggle Help");
[self toggleHelpAnimated:YES];
break;
}
case 1: {
inf(@"Action: FAQ");
[self setHelpChapter:@"faq"];
[self setHelpHidden:NO animated:YES];
break;
}
case 2: {
inf(@"Action: Guide");
[[MPAppDelegate get] showGuide];
break;
}
case 3: {
inf(@"Action: Preferences");
[self performSegueWithIdentifier:@"UserProfile" sender:self];
break;
}
#ifdef ADHOC
case 4: {
inf(@"Action: Feedback via TestFlight");
[TestFlight openFeedbackView];
break;
}
case 5:
#else
case 4: {
ATConnect *connection = [ATConnect sharedConnection];
[connection presentFeedbackControllerFromViewController:self];
inf(@"Action: Feedback via Mail");
if (![MFMailComposeViewController canSendMail])
[PearlAlert showAlertWithTitle:@"Feedback"
message:
@"We'd love to hear what you think!\n\n"
@"Please send any comments or reports to:\n"
@"masterpassword@lyndir.com"
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
otherTitles:nil];
else {
[PearlAlert showAlertWithTitle:@"Feedback"
message:
@"We'd love to hear what you think!\n\n"
@"If you're having trouble, it may help us if you can first reproduce the problem "
@"and then include log files in your message."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
MFMailComposeViewController *composer = [MFMailComposeViewController new];
[composer setMailComposeDelegate:self];
[composer setToRecipients:[NSArray arrayWithObject:@"Master Password Development <masterpassword@lyndir.com>"]];
[composer setSubject:PearlString(@"Feedback for Master Password [%@]", [[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
[composer setMessageBody:
PearlString(
@"\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[MPAppDelegate get].activeUser.name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion)
isHTML:NO];
if (buttonIndex_ == [alert_ firstOtherButtonIndex]) {
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
[composer addAttachmentData:[[[PearlLogger get] formatMessagesWithLevel:logLevel] dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain"
fileName:PearlString(@"%@-%@.log",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
[PearlKeyChain deviceIdentifier])];
}
[self presentModalViewController:composer animated:YES];
}
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
}
break;
}
case 5:
#endif
{
inf(@"Action: Sign out");
[[MPAppDelegate get] signOut];
break;
}
@@ -448,6 +509,17 @@
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error {
if (error)
err(@"Feedback composer error: %@, result: %d", error, result);
else
inf(@"Feedback composer result: %d", result);
[controller dismissViewControllerAnimated:YES completion:nil];
}
- (MPElementType)selectedType {
return self.activeElement.type;
@@ -485,6 +557,8 @@
- (void)didSelectElement:(MPElementEntity *)element {
inf(@"Selected: %@", element.name);
[self closeAlert];
if (element) {
@@ -516,10 +590,10 @@
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement];
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", NSStringFromMPElementType(self.activeElement.type))];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
nil]];
}
[self updateAnimated:YES];
@@ -555,6 +629,7 @@
navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
inf(@"External link: %@", [request URL]);
[TestFlight passCheckpoint:MPCheckpointExternalLink];
[[UIApplication sharedApplication] openURL:[request URL]];

View File

@@ -66,6 +66,7 @@
- (void)viewWillAppear:(BOOL)animated {
inf(@"Preferences will appear");
[self.avatarsView autoSizeContent];
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if (subview.tag && ((UIControl *)subview).selected) {
@@ -79,6 +80,12 @@
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Preferences will disappear");
[super viewWillDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);

View File

@@ -110,6 +110,7 @@
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
dbg(@"Search ended with: %@", controller.searchBar.text);
controller.searchBar.prompt = nil;
controller.searchBar.searchResultsButtonSelected = NO;
}
@@ -345,6 +346,8 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete)
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
inf(@"Deleting element: %@", element.name);
[self.fetchedResultsController.managedObjectContext deleteObject:element];
[TestFlight passCheckpoint:MPCheckpointDeleteElement];

View File

@@ -23,7 +23,10 @@
- (void)viewWillAppear:(BOOL)animated {
inf(@"Type selection will appear");
self.recommendedTipContainer.alpha = 0;
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
@@ -51,6 +54,12 @@
[super viewDidLoad];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Type selection will disappear");
[super viewWillDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;

View File

@@ -142,6 +142,7 @@
- (void)viewWillAppear:(BOOL)animated {
inf(@"Lock screen will appear");
self.selectedUser = nil;
[self updateUsers];
@@ -157,6 +158,7 @@
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Lock screen will disappear");
[super viewWillDisappear:animated];
}

View File

@@ -10,7 +10,7 @@
@interface MPiOSConfig : MPConfig
@property (nonatomic, retain) NSNumber *sendDebugInfo;
@property (nonatomic, retain) NSNumber *sendInfo;
@property (nonatomic, retain) NSNumber *helpHidden;
@property (nonatomic, retain) NSNumber *showQuickStart;

View File

@@ -7,7 +7,7 @@
//
@implementation MPiOSConfig
@dynamic sendDebugInfo, helpHidden, showQuickStart;
@dynamic sendInfo, helpHidden, showQuickStart;
- (id)init {
@@ -15,7 +15,7 @@
return self;
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(sendDebugInfo)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(sendInfo)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
@"510296984", NSStringFromSelector(@selector(iTunesID)),

View File

@@ -48,7 +48,7 @@
<key>Title</key>
<string>Send Diagnostic Info</string>
<key>Key</key>
<string>sendDebugInfo</string>
<string>sendInfo</string>
<key>DefaultValue</key>
<false/>
</dict>