2
0

Improved runtime debug logging + new user avatar selection.

[UPDATED]   Crashlytics.
[IMPROVED]  Sending logs and configuration to crashlytics, added
            sendDebugInfo option that allows the user to choose to send
            more info.  Now also sending a device identifier.
[ADDED]     Avatar selection dialog when a new user is created.
This commit is contained in:
Maarten Billemont
2012-06-10 08:21:41 +02:00
parent a8bf74a925
commit 6f1d53ea35
21 changed files with 276 additions and 165 deletions

View File

@@ -6,8 +6,10 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <Crashlytics/Crashlytics.h>
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "ATConnect.h"
@implementation MPAppDelegate_Shared (Key)
@@ -133,6 +135,18 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
[self storeSavedKeyFor:user];
}
@try {
if ([[MPiOSConfig get].sendDebugInfo boolValue]) {
[TestFlight addCustomEnvironmentInformation:user.name forKey:@"username"];
[[Crashlytics sharedInstance] setValue:user.name forKey:@"username"];
[[ATConnect sharedConnection] addAdditionalInfoToFeedback:user.name withKey:@"username"];
}
}
@catch (id exception) {
err(@"While setting username: %@", exception);
}
user.lastUsed = [NSDate date];
self.activeUser = user;
[[MPAppDelegate_Shared get] saveContext];

View File

@@ -7,7 +7,7 @@
//
#import "MPTypes.h"
#import "MPElementStoredEntity.h"
#import "MPEntities.h"
#define MP_N 131072

View File

@@ -6,6 +6,7 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <Crashlytics/Crashlytics.h>
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
@@ -38,7 +39,7 @@
[MPiOSConfig get];
#ifdef DEBUG
[PearlLogger get].autoprintLevel = PearlLogLevelDebug;
[PearlLogger get].autoprintLevel = PearlLogLevelTrace;
//[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
#endif
}
@@ -52,6 +53,19 @@
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 {
@@ -146,19 +160,28 @@
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
#ifndef DEBUG
//#ifndef DEBUG
@try {
NSString *testFlightToken = [self testFlightToken];
if ([testFlightToken length]) {
dbg(@"Initializing TestFlight");
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
#ifdef ADHOC
[TestFlight setDeviceIdentifier:[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) {
if (message.level >= PearlLogLevelInfo)
PearlLogLevel level = PearlLogLevelInfo;
if ([[MPiOSConfig get].sendDebugInfo boolValue])
level = PearlLogLevelDebug;
if (message.level >= level)
TFLog(@"%@", message);
return YES;
@@ -166,7 +189,7 @@
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
}
}
@catch (NSException *exception) {
@catch (id exception) {
err(@"TestFlight: %@", exception);
}
@try {
@@ -174,10 +197,22 @@
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 (NSException *exception) {
@catch (id exception) {
err(@"Crashlytics: %@", exception);
}
@try {
@@ -199,10 +234,10 @@
}];
}
}
@catch (NSException *exception) {
@catch (id exception) {
err(@"Localytics exception: %@", exception);
}
#endif
//#endif
});
@try {
@@ -214,6 +249,8 @@
[connection setApiKey:apptentiveAPIKey];
[connection setShouldTakeScreenshot:NO];
[connection addAdditionalInfoToFeedback:[PearlInfoPlist get].CFBundleVersion withKey:@"CFBundleVersion"];
[connection addAdditionalInfoToFeedback:[PearlKeyChain deviceIdentifier] withKey:@"deviceIdentifier"];
[connection addAdditionalInfoToFeedback:@"Anonymous" withKey:@"username"];
}
}
@catch (NSException *exception) {
@@ -368,6 +405,8 @@ UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[MPAppDelegate get] checkConfig];
if ([[MPiOSConfig get].showQuickStart boolValue])
[self showGuide];

View File

@@ -103,8 +103,6 @@
}
}];
[[MPAppDelegate get] checkConfig];
[super viewDidAppear:animated];
}

View File

@@ -29,7 +29,6 @@
for (int a = 0; a < MPAvatarCount; ++a) {
UIButton *avatar = [self.avatarTemplate clone];
avatar.togglesSelectionInSuperview = YES;
avatar.tag = a;
avatar.hidden = NO;
avatar.center = CGPointMake(
@@ -37,6 +36,7 @@
self.avatarTemplate.center.y);
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)]
forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor;

View File

@@ -39,6 +39,50 @@
// self.lock.alpha = 0.5f;
// } completion:nil];
- (void)initAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
UIScrollView *alertAvatarScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(12, 30, 260, 150)];
alertAvatarScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
[alertAvatarScrollView flashScrollIndicatorsContinuously];
[alert addSubview:alertAvatarScrollView];
CGPoint selectedOffset = CGPointZero;
for (int a = 0; a < MPAvatarCount; ++a) {
UIButton *avatar = [self.avatarTemplate cloneAddedTo:alertAvatarScrollView];
avatar.tag = a;
avatar.hidden = NO;
avatar.center = CGPointMake(
(20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
20 + self.avatarTemplate.bounds.size.height / 2);
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)] forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 5;
avatar.backgroundColor = [UIColor clearColor];
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected)
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
else
avatar.backgroundColor = [UIColor clearColor];
} options:0];
[avatar onSelect:^(BOOL selected) {
if (selected)
user.avatar = (unsigned)avatar.tag;
} options:0];
avatar.selected = (a == user.avatar);
if (avatar.selected)
selectedOffset = CGPointMake(avatar.center.x - alertAvatarScrollView.bounds.size.width / 2, 0);
}
[alertAvatarScrollView autoSizeContent];
[alertAvatarScrollView setContentOffset:selectedOffset animated:YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
@@ -137,7 +181,6 @@
if (selected)
[self didSelectNewUserAvatar:avatar];
} options:0];
avatar.togglesSelectionInSuperview = YES;
avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y);
avatar.hidden = NO;
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
@@ -151,6 +194,7 @@
avatar.tag = user.avatar;
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)]
forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:YES];
[self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]];
@@ -179,26 +223,32 @@
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
[PearlAlert showAlertWithTitle:@"New User"
message:@"Enter your name:" viewStyle:UIAlertViewStylePlainTextInput
[PearlAlert showAlertWithTitle:@"Enter Your Name"
message:nil viewStyle:UIAlertViewStylePlainTextInput
initAlert:^(UIAlertView *alert, UITextField *firstField) {
firstField.autocapitalizationType = UITextAutocapitalizationTypeWords;
firstField.autocorrectionType = UITextAutocorrectionTypeYes;
firstField.spellCheckingType = UITextSpellCheckingTypeYes;
firstField.keyboardType = UIKeyboardTypeAlphabet;
}
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
newUserAvatar.selected = NO;
if (buttonIndex == [alert cancelButtonIndex])
return;
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newUser.name = [alert textFieldAtIndex:0].text;
[PearlAlert showAlertWithTitle:@"Choose Your Avatar"
message:@"\n\n\n\n\n\n" viewStyle:UIAlertViewStyleDefault
initAlert:^(UIAlertView *_alert, UITextField *firstField) {
[self initAvatarAlert:_alert forUser:newUser];
}
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
newUserAvatar.selected = NO;
self.selectedUser = newUser;
[self updateUsers];
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
}

View File

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

View File

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

View File

@@ -1244,93 +1244,6 @@ L4m3P4sSw0rD</string>
<image name="ui_spinner.png" width="75" height="75"/>
<image name="ui_textfield.png" width="158" height="34"/>
</resources>
<classes>
<class className="IASKAppSettingsViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/IASKAppSettingsViewController.h"/>
<relationships>
<relationship kind="action" name="dismiss:"/>
<relationship kind="outlet" name="delegate"/>
</relationships>
</class>
<class className="MPGuideViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPGuideViewController.h"/>
<relationships>
<relationship kind="action" name="close"/>
<relationship kind="outlet" name="scrollView" candidateClass="UIScrollView"/>
</relationships>
</class>
<class className="MPMainViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPMainViewController.h"/>
<relationships>
<relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="closeAlert"/>
<relationship kind="action" name="copyContent"/>
<relationship kind="action" name="editPassword"/>
<relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="actionsTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertBody" candidateClass="UITextView"/>
<relationship kind="outlet" name="alertContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertTitle" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentField" candidateClass="UITextField"/>
<relationship kind="outlet" name="contentTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentTipEditIcon" candidateClass="UIImageView"/>
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
<relationship kind="outlet" name="searchResultsController" candidateClass="MPSearchDelegate"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="siteName" candidateClass="UILabel"/>
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="typeTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPPreferencesViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPPreferencesViewController.h"/>
<relationships>
<relationship kind="action" name="didToggleSwitch:" candidateClass="UISwitch"/>
<relationship kind="outlet" name="avatarTemplate" candidateClass="UIButton"/>
<relationship kind="outlet" name="avatarsView" candidateClass="UIScrollView"/>
<relationship kind="outlet" name="changeMPCell" candidateClass="UITableViewCell"/>
<relationship kind="outlet" name="exportCell" candidateClass="UITableViewCell"/>
<relationship kind="outlet" name="savePasswordSwitch" candidateClass="UISwitch"/>
</relationships>
</class>
<class className="MPSearchDelegate" superclassName="NSObject">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPSearchDelegate.h"/>
<relationships>
<relationship kind="outlet" name="delegate"/>
<relationship kind="outlet" name="searchDisplayController" candidateClass="UISearchDisplayController"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPTypeViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPTypeViewController.h"/>
<relationships>
<relationship kind="outlet" name="recommendedTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPUnlockViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPUnlockViewController.h"/>
<relationships>
<relationship kind="action" name="deleteTargetedUser:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="avatarTemplate" candidateClass="UIButton"/>
<relationship kind="outlet" name="avatarsView" candidateClass="UIScrollView"/>
<relationship kind="outlet" name="deleteTip" candidateClass="UILabel"/>
<relationship kind="outlet" name="nameLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="oldNameLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordField" candidateClass="UITextField"/>
<relationship kind="outlet" name="passwordTipLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordTipView" candidateClass="UIView"/>
<relationship kind="outlet" name="passwordView" candidateClass="UIView"/>
<relationship kind="outlet" name="spinner" candidateClass="UIImageView"/>
</relationships>
</class>
</classes>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<nil key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>

View File

@@ -6,7 +6,7 @@
<array>
<dict>
<key>FooterText</key>
<string>http://masterpassword.lyndir.com</string>
<string>If you&apos;re experiencing problems, enabling this will send us details that can help diagnose and resolve them. Great care has been taken to guarantee no private information is ever sent.</string>
<key>Title</key>
<string></string>
<key>Type</key>
@@ -42,6 +42,16 @@
<key>Key</key>
<string>unset</string>
</dict>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Send Diagnostic Info</string>
<key>Key</key>
<string>sendDebugInfo</string>
<key>DefaultValue</key>
<false/>
</dict>
<dict>
<key>FooterText</key>
<string>This causes your master password to be remembered while your device is powered on. Similar to your phone&apos;s SIM lock, you only need to enter the password once after powering on.</string>