2
0

Threading fixes for Mac, spinner fix for iOS.

[UPDATED]   TestFlight.
[FIXED]     Mac: References to MPAppDelegate that should be OS-independant now use MPAppDelegate_Shared.
[FIXED]     Mac: Threading and content UI updates.
[FIXED]     iOS: Spinner was showing when going back to unlock VC.
This commit is contained in:
Maarten Billemont
2013-04-27 00:34:28 -04:00
parent 291b408995
commit 5d5e9395b3
13 changed files with 370 additions and 191 deletions

View File

@@ -6,7 +6,7 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPAppDelegate.h"
#import "MPAppDelegate_Shared.h"
@implementation MPConfig
@@ -25,7 +25,7 @@
NSStringFromSelector( @selector(iCloudDecided) ) : @NO
}];
self.delegate = [MPAppDelegate get];
self.delegate = [MPAppDelegate_Shared get];
return self;
}

View File

@@ -7,7 +7,7 @@
//
#import "MPEntities.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Shared.h"
@implementation NSManagedObjectContext(MP)
@@ -112,7 +112,7 @@
- (id)content {
MPKey *key = [MPAppDelegate get].key;
MPKey *key = [MPAppDelegate_Shared get].key;
if (!key)
return nil;
@@ -122,7 +122,7 @@
- (void)setContent:(id)content {
MPKey *key = [MPAppDelegate get].key;
MPKey *key = [MPAppDelegate_Shared get].key;
if (!key)
return;

View File

@@ -8,12 +8,7 @@
#import <Cocoa/Cocoa.h>
@interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate> {
NSString *_content;
}
@property(nonatomic, strong) NSString *content;
@interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate>
@property(nonatomic, weak) IBOutlet NSTextField *siteField;
@property(nonatomic, weak) IBOutlet NSTextField *contentField;

View File

@@ -16,13 +16,14 @@
@interface MPPasswordWindowController()
@property(nonatomic, strong) NSArray /* MPElementEntity */ *siteResults;
@property(nonatomic) BOOL inProgress;
@property(nonatomic) BOOL siteFieldPreventCompletion;
@end
@implementation MPPasswordWindowController
@implementation MPPasswordWindowController {
NSManagedObjectID *_activeElementOID;
}
- (void)windowDidLoad {
@@ -30,7 +31,7 @@
self.window.styleMask = NSHUDWindowMask | NSTitledWindowMask | NSUtilityWindowMask | NSClosableWindowMask;
else
self.window.styleMask = NSTexturedBackgroundWindowMask | NSResizableWindowMask | NSTitledWindowMask | NSClosableWindowMask;
[self setContent:@""];
[self.tipField setStringValue:@""];
@@ -110,19 +111,23 @@
switch (returnCode) {
case NSAlertAlternateReturn:
// "Change" button.
if ([[NSAlert alertWithMessageText:@"Changing Master Password"
defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil
informativeTextWithFormat:
@"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."] runModal]
== 1) {
{
NSInteger returnCode_ = [[NSAlert
alertWithMessageText:@"Changing Master Password" defaultButton:nil
alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil informativeTextWithFormat:
@"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."]
runModal];
if (returnCode_ == NSAlertDefaultReturn) {
activeUser.keyID = nil;
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[[MPMacAppDelegate get] signOutAnimated:YES];
}
}
break;
case NSAlertOtherReturn:
@@ -137,7 +142,7 @@
self.inProgress = YES;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0 ), ^{
BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser
usingMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]];
usingMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]];
self.inProgress = NO;
dispatch_async( dispatch_get_main_queue(), ^{
@@ -171,29 +176,31 @@
if (![query length] || ![MPMacAppDelegate get].key)
return nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(name BEGINSWITH[cd] %@) AND user == %@",
query, [[MPMacAppDelegate get] activeUserForThread]];
__block NSMutableArray *mutableResults = [NSMutableArray array];
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(name BEGINSWITH[cd] %@) AND user == %@",
query, [[MPMacAppDelegate get] activeUserInContext:context]];
NSError *error = nil;
self.siteResults = [[MPMacAppDelegate managedObjectContextForThreadIfReady] executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"While fetching elements for completion: %@", error);
NSError *error = nil;
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"While fetching elements for completion: %@", error);
if ([self.siteResults count] == 1) {
[textView setString:[(MPElementEntity *)[self.siteResults objectAtIndex:0] name]];
[textView setSelectedRange:NSMakeRange( [query length], [[textView string] length] - [query length] )];
if ([self trySite])
return nil;
if (siteResults) {
for (MPElementEntity *element in siteResults)
[mutableResults addObject:element.name];
//[mutableResults addObject:query]; // For when the app should be able to create new sites.
}
}];
if ([mutableResults count] == 1) {
//[textView setString:[(MPElementEntity *)[siteResults objectAtIndex:0] name]];
//[textView setSelectedRange:NSMakeRange( [query length], [[textView string] length] - [query length] )];
[self trySiteAndCopyContent:NO];
}
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
if (self.siteResults)
for (MPElementEntity *element in self.siteResults)
[mutableResults addObject:element.name];
// [mutableResults addObject:query]; // For when the app should be able to create new sites.
return mutableResults;
}
@@ -205,9 +212,8 @@
}
if ((self.siteFieldPreventCompletion = [NSStringFromSelector( commandSelector ) hasPrefix:@"delete"]))
return NO;
if (commandSelector == @selector(insertNewline:) && [self.content length]) {
if ([self trySite])
[self copyContents];
if (commandSelector == @selector(insertNewline:)) {
[self trySiteAndCopyContent:YES];
return YES;
}
@@ -215,26 +221,6 @@
}
- (void)copyContents {
[[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
if (![[NSPasteboard generalPasteboard] setString:self.content forType:NSPasteboardTypeString]) {
wrn(@"Couldn't copy password to pasteboard.");
return;
}
dispatch_async( dispatch_get_main_queue(), ^{
self.tipField.alphaValue = 1;
[self.tipField setStringValue:@"Copied! Hit ⎋ (ESC) to close window."];
dispatch_time_t popTime = dispatch_time( DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC) );
dispatch_after( popTime, dispatch_get_main_queue(), ^{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.2f];
[self.tipField.animator setAlphaValue:0];
[NSAnimationContext endGrouping];
} );
} );
[[self findElement] use];
}
- (void)controlTextDidEndEditing:(NSNotification *)note {
@@ -242,7 +228,7 @@
if (note.object != self.siteField)
return;
[self trySite];
[self trySiteAndCopyContent:NO];
}
- (void)controlTextDidChange:(NSNotification *)note {
@@ -251,13 +237,12 @@
return;
// Update the site content as the site name changes.
BOOL hasValidSite = [self trySite];
BOOL enterPressed = [[NSApp currentEvent] type] == NSKeyDown &&
[[[NSApp currentEvent] charactersIgnoringModifiers] isEqualToString:@"\r"];
[self trySiteAndCopyContent:enterPressed];
if ([[NSApp currentEvent] type] == NSKeyDown && [[[NSApp currentEvent] charactersIgnoringModifiers] isEqualToString:@"\r"]) {
if (hasValidSite)
[self copyContents];
if (enterPressed)
return;
}
if (self.siteFieldPreventCompletion) {
self.siteFieldPreventCompletion = NO;
@@ -269,43 +254,70 @@
self.siteFieldPreventCompletion = NO;
}
- (NSString *)content {
- (MPElementEntity *)activeElementForThread {
return _content;
if (!_activeElementOID)
return nil;
NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady];
if (!moc)
return nil;
NSError *error;
MPElementEntity *activeElement = (MPElementEntity *)[moc existingObjectWithID:_activeElementOID error:&error];
if (!activeElement)
err(@"Couldn't retrieve active element: %@", error);
return activeElement;
}
- (void)setContent:(NSString *)content {
_content = content;
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
paragraph.alignment = NSCenterTextAlignment;
[self.contentField setAttributedStringValue:
[[NSAttributedString alloc] initWithString:_content
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
paragraph, NSParagraphStyleAttributeName,
nil]]];
[self.contentField setAttributedStringValue:[[NSAttributedString alloc] initWithString:content attributes:@{
NSParagraphStyleAttributeName : paragraph
}]];
}
- (BOOL)trySite {
- (void)trySiteAndCopyContent:(BOOL)copyContent {
MPElementEntity *result = [self findElement];
if (!result) {
[self setContent:@""];
[self.tipField setStringValue:@""];
return NO;
}
[self setContent:@""];
[self.tipField setStringValue:@"Generating..."];
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
NSString *description = [result.content description];
if (!description)
description = @"";
NSString *content = [[self activeElementForThread].content description];
if (!content)
content = @"";
if (copyContent) {
[[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
if (![[NSPasteboard generalPasteboard] setString:content forType:NSPasteboardTypeString]) {
wrn(@"Couldn't copy password to pasteboard.");
return;
}
MPElementEntity *activeElement = [self activeElementForThread];
[activeElement use];
[activeElement.managedObjectContext saveToStore];
}
dispatch_async( dispatch_get_main_queue(), ^{
[self setContent:description];
[self.tipField setStringValue:@"Hit ⌤ (ENTER) to copy the password."];
[self setContent:content];
self.tipField.alphaValue = 1;
if (!copyContent)
[self.tipField setStringValue:@"Hit ⌤ (ENTER) to copy the password."];
else {
[self.tipField setStringValue:@"Copied! Hit ⎋ (ESC) to close window."];
dispatch_time_t popTime = dispatch_time( DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC) );
dispatch_after( popTime, dispatch_get_main_queue(), ^{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.2f];
[self.tipField.animator setAlphaValue:0];
[NSAnimationContext endGrouping];
} );
}
} );
} );
@@ -329,17 +341,6 @@
});
}];
*/
return YES;
}
- (MPElementEntity *)findElement {
for (MPElementEntity *element in self.siteResults)
if ([element.name isEqualToString:[self.siteField stringValue]])
return element;
return nil;
}
@end

View File

@@ -126,7 +126,6 @@
self.tip.text = @"";
self.nameLabel.layer.cornerRadius = 5;
self.avatarTemplate.hidden = YES;
self.spinner.alpha = 0;
self.passwordTipView.hidden = NO;
self.createPasswordTipView.hidden = NO;
[self.emergencyPassword setTitle:@"" forState:UIControlStateNormal];
@@ -200,13 +199,14 @@
[self updateUsers];
self.uiContainer.alpha = 0;
self.spinner.alpha = 0;
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
if (!animated)
if (!animated && !self.navigationController.presentedViewController)
[[self findTargetedAvatar] setSelected:YES];
else
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];

View File

@@ -611,47 +611,6 @@
postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey ) userInfo:nil];
}
#pragma mark - UbiquityStoreManagerDelegate
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
[super ubiquityStoreManager:manager willLoadStoreIsCloud:isCloudStore];
if (!isCloudStore && ![[MPConfig get].iCloudDecided boolValue])
[self alertCloudDisabledForManager:manager];
}
- (void)alertCloudDisabledForManager:(UbiquityStoreManager *)manager {
[PearlAlert showAlertWithTitle:@"iCloud" message:
@"iCloud is now disabled.\n\n"
@"It is highly recommended you enable iCloud."
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex] + 0) {
[PearlAlert showAlertWithTitle:@"About iCloud" message:
@"iCloud is Apple's solution for saving your data in \"the cloud\" "
@"and making sure your other iPhones, iPads and Macs are in sync.\n\n"
@"For Master Password, that means your sites are available on all your "
@"Apple devices, and you always have a backup of them in case "
@"you lose one or need to restore.\n\n"
@"Thanks to the way Master Password works, it doesn't need to send your "
@"site's passwords to Apple for the backup to work: Only their names are "
@"saved. If you set a custom password it will be sent to iCloud after "
@"being encrypted with your master password.\n\n"
@"Apple can never see any of your passwords."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
[self alertCloudDisabledForManager:manager];
}
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
return;
}
[MPConfig get].iCloudDecided = @YES;
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
manager.cloudEnabled = YES;
} cancelTitle:@"Leave Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
}
#pragma mark - Google+

View File

@@ -2810,6 +2810,190 @@ However, it means that anyone who finds your device unlocked can do the same.</s
<image name="ui_textfield.png" width="158" height="34"/>
<image name="unlocked.png" width="84" height="80"/>
</resources>
<classes>
<class className="MPAppViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPAppViewController.h"/>
<relationships>
<relationship kind="action" name="deblock:" candidateClass="UIButton"/>
<relationship kind="action" name="gorillas:" candidateClass="UIButton"/>
</relationships>
</class>
<class className="MPAppsViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPAppsViewController.h"/>
<relationships>
<relationship kind="action" name="exit"/>
<relationship kind="outlet" name="pagePositionView" candidateClass="UIImageView"/>
</relationships>
</class>
<class className="MPElementListAllViewController" superclassName="MPElementListController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPElementListAllViewController.h"/>
<relationships>
<relationship kind="action" name="add:"/>
<relationship kind="action" name="close:"/>
<relationship kind="outlet" name="navigationBar" candidateClass="UINavigationBar"/>
</relationships>
</class>
<class className="MPElementListController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPElementListController.h"/>
<relationships>
<relationship kind="outlet" name="delegate"/>
</relationships>
</class>
<class className="MPElementListSearchController" superclassName="MPElementListController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPElementListSearchController.h"/>
<relationships>
<relationship kind="outlet" name="searchDisplayController" candidateClass="UISearchDisplayController"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPGuideViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPGuideViewController.h"/>
<relationships>
<relationship kind="action" name="close"/>
<relationship kind="action" name="play"/>
<relationship kind="action" name="toggleVolume"/>
<relationship kind="outlet" name="content" candidateClass="UIView"/>
<relationship kind="outlet" name="contentButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="contentText" candidateClass="UITextField"/>
<relationship kind="outlet" name="contentTip" candidateClass="UIView"/>
<relationship kind="outlet" name="contentTipText" candidateClass="UILabel"/>
<relationship kind="outlet" name="largePlayButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="progress" candidateClass="UIProgressView"/>
<relationship kind="outlet" name="siteNameTip" candidateClass="UIView"/>
<relationship kind="outlet" name="smallPlayButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="toolButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="toolTip" candidateClass="UIView"/>
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="typeTip" candidateClass="UIView"/>
<relationship kind="outlet" name="usernameButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="usernameTip" candidateClass="UIView"/>
<relationship kind="outlet" name="volumeButton" candidateClass="UIButton"/>
</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="closeOutdatedAlert"/>
<relationship kind="action" name="copyContent"/>
<relationship kind="action" name="editLoginName:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="editPassword"/>
<relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="action" name="infoOutdatedAlert"/>
<relationship kind="action" name="panHelpDown:" candidateClass="UIPanGestureRecognizer"/>
<relationship kind="action" name="panHelpUp:" candidateClass="UIPanGestureRecognizer"/>
<relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="searchOutdatedElements"/>
<relationship kind="action" name="toggleUser"/>
<relationship kind="action" name="upgradePassword"/>
<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="displayContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="loginNameContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="loginNameField" candidateClass="UITextField"/>
<relationship kind="outlet" name="loginNameTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="loginNameTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="outdatedAlertBack" candidateClass="UIImageView"/>
<relationship kind="outlet" name="outdatedAlertCloseButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="outdatedAlertContainer" candidateClass="UIView"/>
<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="passwordUpgrade" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordUser" candidateClass="UIButton"/>
<relationship kind="outlet" name="pullDownGesture" candidateClass="UIPanGestureRecognizer"/>
<relationship kind="outlet" name="pullDownView" candidateClass="UIImageView"/>
<relationship kind="outlet" name="pullUpGesture" candidateClass="UIPanGestureRecognizer"/>
<relationship kind="outlet" name="pullUpView" candidateClass="UIImageView"/>
<relationship kind="outlet" name="searchDelegate" candidateClass="MPElementListSearchController"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="siteName" candidateClass="UILabel"/>
<relationship kind="outlet" name="toolTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="toolTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="toolTipEditIcon" candidateClass="UIImageView"/>
<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="action" name="settings:"/>
<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="defaultTypeLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="exportCell" candidateClass="UITableViewCell"/>
<relationship kind="outlet" name="savePasswordSwitch" candidateClass="UISwitch"/>
</relationships>
</class>
<class className="MPSetupViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPSetupViewController.h"/>
<relationships>
<relationship kind="action" name="close:" candidateClass="UIBarButtonItem"/>
<relationship kind="outlet" name="cloudSwitch" candidateClass="UISwitch"/>
<relationship kind="outlet" name="rememberLoginSwitch" candidateClass="UISwitch"/>
</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="add:" candidateClass="UIButton"/>
<relationship kind="action" name="emergencyClose:" candidateClass="UIButton"/>
<relationship kind="action" name="emergencyCopy:" candidateClass="UIButton"/>
<relationship kind="action" name="facebook:" candidateClass="UIButton"/>
<relationship kind="action" name="google:" candidateClass="UIButton"/>
<relationship kind="action" name="mail:" candidateClass="UIButton"/>
<relationship kind="action" name="targetedUserAction:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="twitter:" candidateClass="UIButton"/>
<relationship kind="outlet" name="avatarTemplate" candidateClass="UIButton"/>
<relationship kind="outlet" name="avatarsView" candidateClass="UIScrollView"/>
<relationship kind="outlet" name="createPasswordTipView" candidateClass="UIView"/>
<relationship kind="outlet" name="emergencyActivity" candidateClass="UIActivityIndicatorView"/>
<relationship kind="outlet" name="emergencyContentTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="emergencyCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="emergencyCounterStepper" candidateClass="UIStepper"/>
<relationship kind="outlet" name="emergencyGeneratorContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="emergencyMasterPassword" candidateClass="UITextField"/>
<relationship kind="outlet" name="emergencyName" candidateClass="UITextField"/>
<relationship kind="outlet" name="emergencyPassword" candidateClass="UIButton"/>
<relationship kind="outlet" name="emergencySite" candidateClass="UITextField"/>
<relationship kind="outlet" name="emergencyTypeControl" candidateClass="UISegmentedControl"/>
<relationship kind="outlet" name="nameLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="newsView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="oldNameLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordField" candidateClass="UITextField"/>
<relationship kind="outlet" name="passwordFieldLabel" candidateClass="UILabel"/>
<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"/>
<relationship kind="outlet" name="targetedUserActionGesture" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="tip" candidateClass="UILabel"/>
<relationship kind="outlet" name="uiContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="wordWall" candidateClass="UIView"/>
</relationships>
</class>
<class className="PearlNavigationController" superclassName="UINavigationController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/PearlNavigationController.h"/>
</class>
</classes>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<nil key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>