2
0

Compare commits

...

7 Commits

Author SHA1 Message Date
Maarten Billemont
bad54f2c0c Add an informational message for testers. 2012-02-29 02:10:46 +01:00
Maarten Billemont
eae8e9b7c4 Lockscreen improvements.
[FIXED]     Switching between guide and unlock screen.
[ADDED]     Sign out button in action so you can sign in with a
            different master password.
[REMOVED]   Change password from MPConfig.  There are better facilities
            for this now.
[FIXED]     Image names in the lockscreen.
2012-02-29 01:56:58 +01:00
Maarten Billemont
2ff3f0804b Make Pearl publicly accessible. 2012-02-27 23:56:42 +01:00
Maarten Billemont
50da5c0cb1 Update for prefixed Pearl. 2012-02-27 23:38:28 +01:00
Maarten Billemont
f3196841f3 TestFlight update + search improvements.
[UPDATED]   TestFlight SDK updated to 0.8.3
[ADDED]     Log >=info messages to TestFlight
[ADDED]     Hide status bar while locked.
[ADDED]     Show all sites when search results icon is tapped.
[IMPROVED]  Search table cells.
2012-02-26 23:01:17 +01:00
Maarten Billemont
039ec9b082 Fancy master password input screen.
[ADDED]     Fancy master password input screen.
[FIXED]     Key size of stored passwords.
[FIXED]     Several UI fixes.
[FIXED]     The counter wasn't correctly added to the cipherKey.
[IMPROVED]  Site style improvements.
[UPDATED]   Site algorithm explanation update.
2012-02-25 15:44:37 +01:00
Maarten Billemont
ad9c52896d Use scrypt for deriving a safer key from the master password + website. 2012-02-13 10:28:47 +01:00
492 changed files with 4106 additions and 2271 deletions

2
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "External/Pearl"] [submodule "External/Pearl"]
path = External/Pearl path = External/Pearl
url = git@github.com:Lyndir/Pearl.git url = git://github.com/Lyndir/Pearl.git
[submodule "External/InAppSettingsKit"] [submodule "External/InAppSettingsKit"]
path = External/InAppSettingsKit path = External/InAppSettingsKit
url = git://github.com/futuretap/InAppSettingsKit.git url = git://github.com/futuretap/InAppSettingsKit.git

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 KiB

After

Width:  |  Height:  |  Size: 172 KiB

2
External/Pearl vendored

View File

@@ -1,4 +1,4 @@
Thanks for downloading the TestFlight SDK 0.8.2! Thanks for downloading the TestFlight SDK 0.8.3!
This document is also available on the web at https://www.testflightapp.com/sdk/doc This document is also available on the web at https://www.testflightapp.com/sdk/doc
@@ -12,6 +12,7 @@ This document is also available on the web at https://www.testflightapp.com/sdk/
8. View your results 8. View your results
9. Advanced Exception Handling 9. Advanced Exception Handling
10. Remote Logging 10. Remote Logging
11. iOS 3
START START
@@ -45,7 +46,7 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
3. Set Folders to "Create groups for any added folders" 3. Set Folders to "Create groups for any added folders"
4. Select all targets that you want to add the SDK to 4. Select all targets that you want to add the SDK to
2. Verify that libTestFlight.a has been added to the Link Binary With Libraries Build Phase for the targets you want to use the SDK with 2. Verify that libTestFlight.a and has been added to the Link Binary With Libraries Build Phase for the targets you want to use the SDK with
1. Select your Project in the Project Navigator 1. Select your Project in the Project Navigator
2. Select the target you want to enable the SDK for 2. Select the target you want to enable the SDK for
@@ -54,15 +55,26 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
5. If libTestFlight.a is not listed, drag and drop the library from your Project Navigator to the Link Binary With Libraries area 5. If libTestFlight.a is not listed, drag and drop the library from your Project Navigator to the Link Binary With Libraries area
6. Repeat Steps 2 - 5 until all targets you want to use the SDK with have the SDK linked 6. Repeat Steps 2 - 5 until all targets you want to use the SDK with have the SDK linked
3. In your Application Delegate: 3. Add libz to your Link Binary With Libraries Build Phase
1. Select your Project in the Project Navigator
2. Select the target you want to enable the SDK for
3. Select the Build Phases tab
4. Open the Link Binary With Libraries Phase
5. Click the + to add a new library
6. Find libz.dylib in the list and add it
7. Repeat Steps 2 - 6 until all targets you want to use the SDK with have libz.dylib
4. In your Application Delegate:
1. Import TestFlight: `#import "TestFlight.h"` 1. Import TestFlight: `#import "TestFlight.h"`
NOTE: If you do not want to import TestFlight.h in every file you may add the above line into you pre-compiled header (`<projectname>_Prefix.pch`) file inside of the NOTE: If you do not want to import TestFlight.h in every file you may add the above line into you pre-compiled header (`<projectname>_Prefix.pch`) file inside of the
#ifdef __OBJC__ section. #ifdef __OBJC__
section.
This will give you access to the SDK across all files. This will give you access to the SDK across all files.
2. Get your Team Token which you can find at [http://testflightapp.com/dashboard/team/](http://testflightapp.com/dashboard/team/) select the team you are using from the team selection drop down list on the top of the page and then select edit. 2. Get your Team Token which you can find at [http://testflightapp.com/dashboard/team/](http://testflightapp.com/dashboard/team/) select the team you are using from the team selection drop down list on the top of the page and then select Team Info.
3. Launch TestFlight with your Team Token 3. Launch TestFlight with your Team Token
@@ -78,9 +90,9 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
4. To report crashes to you we install our own uncaught exception handler. If you are not currently using an exception handler of your own then all you need to do is go to the next step. If you currently use an Exception Handler, or you use another framework that does please go to the section on advanced exception handling. 4. To report crashes to you we install our own uncaught exception handler. If you are not currently using an exception handler of your own then all you need to do is go to the next step. If you currently use an Exception Handler, or you use another framework that does please go to the section on advanced exception handling.
4. To enable the best crash reporting possible we recommend setting the following project build settings in Xcode to NO for all targets that you want to have live crash reporting for. You can find build settings by opening the Project Navigator (default command+1 or command+shift+j) then clicking on the project you are configuring (usually the first selection in the list). From there you can choose to either change the global project settings or settings on an individual project basis. All settings below are in the Deployment Section. 5. To enable the best crash reporting possible we recommend setting the following project build settings in Xcode to NO for all targets that you want to have live crash reporting for. You can find build settings by opening the Project Navigator (default command+1 or command+shift+j) then clicking on the project you are configuring (usually the first selection in the list). From there you can choose to either change the global project settings or settings on an individual project basis. All settings below are in the Deployment Section.
1. Deployment Post Processing 1. Deployment Postrocessing
2. Strip Debug Symbols During Copy 2. Strip Debug Symbols During Copy
3. Strip Linked Product 3. Strip Linked Product
@@ -99,6 +111,15 @@ To launch unguided feedback call the `openFeedbackView` method. We recommend tha
[TestFlight openFeedbackView]; [TestFlight openFeedbackView];
} }
If you want to create your own feedback form you can use the `submitCustomFeedback` method to submit the feedback that the user has entered.
-(IBAction)submitFeedbackPressed:(id)sender {
NSString *feedback = [self getUserFeedback];
[TestFlight submitCustomFeedback:feedback];
}
The above sample assumes that [self getUserFeedback] is implemented such that it obtains the users feedback from the GUI element you have created and that submitFeedbackPressed is the action for your submit button.
Once users have submitted feedback from inside of the application you can view it in the feedback area of your build page. Once users have submitted feedback from inside of the application you can view it in the feedback area of your build page.
6. Upload your build. 6. Upload your build.
@@ -183,11 +204,19 @@ We have implemented three different loggers.
Each of the loggers log asynchronously and all TFLog calls are non blocking. The TestFlight logger writes its data to a file which is then sent to our servers on Session End events. The Apple System Logger sends its messages to the Apple System Log and are viewable using the Organizer in Xcode when the device is attached to your computer. The ASL logger can be disabled by turning it off in your TestFlight options Each of the loggers log asynchronously and all TFLog calls are non blocking. The TestFlight logger writes its data to a file which is then sent to our servers on Session End events. The Apple System Logger sends its messages to the Apple System Log and are viewable using the Organizer in Xcode when the device is attached to your computer. The ASL logger can be disabled by turning it off in your TestFlight options
[TestFlight setOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:@"logsToConsole"]]; [TestFlight setOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:@"logToConsole"]];
The default option is YES. The default option is YES.
The STDERR logger sends log messages to STDERR so that you can see your log statements while debugging. The STDERR logger is only active when a debugger is attached to your application. The STDERR logger sends log messages to STDERR so that you can see your log statements while debugging. The STDERR logger is only active when a debugger is attached to your application. If you do not wish to use the STDERR logger you can disable it by turning it off in your TestFlight options
[TestFlight setOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:@"logToSTDERR"]];
The default option is YES.
11. iOS3
We now require that anyone who is writing an application that supports iOS3 add the System.framework as an optional link. In order to provide a better shutdown experience we send any large log files to our servers in the background. To add System.framework as an optional link you can follow
END END

View File

@@ -6,7 +6,7 @@
// Copyright 2011 TestFlight. All rights reserved. // Copyright 2011 TestFlight. All rights reserved.
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#define TESTFLIGHT_SDK_VERSION @"0.8.2" #define TESTFLIGHT_SDK_VERSION @"0.8.3"
#undef TFLog #undef TFLog
#if __cplusplus #if __cplusplus
@@ -51,7 +51,9 @@ extern "C" {
* library installs crash handlers overtop of the TestFlight Crash Handlers * library installs crash handlers overtop of the TestFlight Crash Handlers
* logToConsole [ NSNumber numberWithBool:YES ] YES - default, sends log statements to Apple System Log and TestFlight log * logToConsole [ NSNumber numberWithBool:YES ] YES - default, sends log statements to Apple System Log and TestFlight log
* NO - sends log statements to TestFlight log only * NO - sends log statements to TestFlight log only
* sendLogOnlyOnCrash [ NSNumber numberWithBool:YES ] NO - default, sends logs to TestFlight at the end of every session * logToSTDERR [ NSNumber numberWithBool:YES ] YES - default, sends log statements to STDERR when debugger is attached
* NO - sends log statements to TestFlight log only
* sendLogOnlyOnCrash [ NSNumber numberWithBool:YES ] NO - default, sends logs to TestFlight at the end of every session
* YES - sends logs statements to TestFlight only if there was a crash * YES - sends logs statements to TestFlight only if there was a crash
*/ */
+ (void)setOptions:(NSDictionary*)options; + (void)setOptions:(NSDictionary*)options;
@@ -68,4 +70,12 @@ extern "C" {
*/ */
+ (void)openFeedbackView; + (void)openFeedbackView;
/**
* Submits custom feedback to the site. Sends the data in feedback to the site. This is to be used as the method to submit
* feedback from custom feedback forms.
*
* @param feedback Your users feedback, method does nothing if feedback is nil
*/
+ (void)submitFeedback:(NSString*)feedback;
@end @end

Binary file not shown.

View File

@@ -1,3 +1,41 @@
0.8.3 - February 14, 2012
Rolled previous beta code into release builds
No longer allow in application updates to occur in applications that were obtained from the app store.
Tested compiled library with:
Xcode 4.3
Xcode 4.2
Xcode 4.1
Xcode 3.2.6
0.8.3 BETA 5 - February 10, 2012
Changed logging from asynchronous to synchronous.
Resolved crash when looking for a log path failed.
Added submitFeedback to the TestFlight class to allow for custom feedback forms.
0.8.3 BETA 4 - January 20, 2012
Resolved an issue that occured when an application was upgraded from 0.8.3 BETA 1 to 0.8.3 BETA 3+ with unsent data from 0.8.3 BETA 1
0.8.3 BETA 3 - January 19, 2012
On crash log files over 64k will not be sent until next launch.
Known Issues:
Logging massive amounts of data at the end of a session may prevent the application from launching in time on next launch
0.8.3 BETA 2 - January 13, 2012
libz.dylib is now required to be added to your "Link Binary with Libraries" build phase
Log file compression, The compression is done on an as needed basis rather than before sending
Changed all outgoing data from JSON to MessagePack
Added option logToSTDERR to disable the STDERR logger
0.8.3 BETA 1 - December 29, 2011
In rare occurrences old session data that had not been sent to our server may have been discarded or attached to the wrong build. It is now no longer discarded
Made sending of Session End events more robust
Network queuing system does better bursting of unsent data
Log files that are larger than 64K are now sent sometime after the next launch
Log files that are larger than 16MB are no longer supported and will be replaced with a message indicating the log file was too large
Fixed crashes while resuming from background
0.8.2 - December 20, 2011 0.8.2 - December 20, 2011
Promoted 0.8.2 BETA 4 to stable Promoted 0.8.2 BETA 4 to stable
@@ -5,7 +43,7 @@ Known Issues:
Under some circumstances Session End events may not be sent until the next launch. Under some circumstances Session End events may not be sent until the next launch.
With large log files Session End events may take a long time to show up. With large log files Session End events may take a long time to show up.
Tested compiled library with Tested compiled library with:
Xcode 4.3 Xcode 4.3
Xcode 4.2 Xcode 4.2
Xcode 4.1 Xcode 4.1
@@ -13,11 +51,10 @@ Xcode 3.2.6
0.8.2 BETA 4 - December 12, 2011 0.8.2 BETA 4 - December 12, 2011
Prevented "The string argument is NULL" from occuring during finishedHandshake in rare cases Prevented "The string argument is NULL" from occuring during finishedHandshake in rare cases
Resolved issue where background data may not be sent Resolved issue where data recorded while offline may not be sent
0.8.2 BETA 3 - December 8, 2011 0.8.2 BETA 3 - December 8, 2011
Added auto-release pools to background setup and tear down Added auto-release pools to background setup and tear down
Added C++ specific code to TFLog declaration
0.8.2 BETA 2 - December 5, 2011 0.8.2 BETA 2 - December 5, 2011
Fixed the "pointer being freed was not allocated" bug Fixed the "pointer being freed was not allocated" bug
@@ -28,7 +65,7 @@ Fixed an issue where Session End events may not be sent until next launch
Fixed an issue where duplicate events could be sent Fixed an issue where duplicate events could be sent
Fixed an issue with Session End events not being sent from some iPod touch models Fixed an issue with Session End events not being sent from some iPod touch models
Tested compiled library with Tested compiled library with:
Xcode 4.2 Xcode 4.2
Xcode 4.1 Xcode 4.1
Xcode 3.2.6 Xcode 3.2.6
@@ -46,7 +83,7 @@ Fixed compability issues with iOS 3
Added calling into the rootViewController shouldAutorotateToInterfaceOrientation if a rootViewController is set Added calling into the rootViewController shouldAutorotateToInterfaceOrientation if a rootViewController is set
Made the comments in TestFlight.h compatible with Appledoc Made the comments in TestFlight.h compatible with Appledoc
Tested compiled library with Tested compiled library with:
Xcode 4.2 Xcode 4.2
Xcode 4.1 Xcode 4.1
Xcode 3.2 Xcode 3.2

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
version = "1.3"> version = "1.3">
<BuildAction <BuildAction>
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForRunning = "YES">
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E" BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
@@ -21,64 +15,19 @@
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Production">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction <LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
buildConfiguration = "Production"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Production"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Production"> buildConfiguration = "Production">
</AnalyzeAction> <BuildableProductRunnable>
<ArchiveAction <BuildableReference
buildConfiguration = "Production" BuildableIdentifier = "primary"
revealArchiveInOrganizer = "YES"> BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
</ArchiveAction> BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
</Scheme> </Scheme>

View File

@@ -1,16 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
version = "1.3"> version = "1.3">
<BuildAction <BuildAction>
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForRunning = "YES">
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E" BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
@@ -21,64 +15,19 @@
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction <LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Debug">
</AnalyzeAction> <BuildableProductRunnable>
<ArchiveAction <BuildableReference
buildConfiguration = "Release" BuildableIdentifier = "primary"
revealArchiveInOrganizer = "YES"> BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
</ArchiveAction> BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
</Scheme> </Scheme>

View File

@@ -8,14 +8,14 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface MPAppDelegate : AbstractAppDelegate @interface MPAppDelegate : PearlAppDelegate
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (strong, nonatomic) NSString *keyPhrase; @property (readonly, strong, nonatomic) NSData *keyPhrase;
@property (strong, nonatomic) NSData *keyPhraseHash; @property (readonly, strong, nonatomic) NSData *keyPhraseHash;
@property (strong, nonatomic) NSString *keyPhraseHashHex; @property (readonly, strong, nonatomic) NSString *keyPhraseHashHex;
+ (MPAppDelegate *)get; + (MPAppDelegate *)get;
+ (NSManagedObjectModel *)managedObjectModel; + (NSManagedObjectModel *)managedObjectModel;
@@ -25,6 +25,10 @@
- (NSURL *)applicationDocumentsDirectory; - (NSURL *)applicationDocumentsDirectory;
- (void)showGuide; - (void)showGuide;
- (void)loadKeyPhrase; - (void)loadKeyPhrase:(BOOL)animated;
- (void)signOut;
- (void)forgetKeyPhrase;
- (NSData *)keyPhraseWithLength:(NSUInteger)keyLength;
- (BOOL)tryMasterPassword:(NSString *)tryPassword;
@end @end

View File

@@ -13,12 +13,15 @@
@interface MPAppDelegate () @interface MPAppDelegate ()
@property (strong, nonatomic) NSData *keyPhrase;
@property (strong, nonatomic) NSData *keyPhraseHash;
@property (strong, nonatomic) NSString *keyPhraseHashHex;
+ (NSDictionary *)keyPhraseQuery; + (NSDictionary *)keyPhraseQuery;
+ (NSDictionary *)keyPhraseHashQuery; + (NSDictionary *)keyPhraseHashQuery;
- (void)forgetKeyPhrase;
- (void)loadStoredKeyPhrase; - (void)loadStoredKeyPhrase;
- (void)askKeyPhrase; - (void)askKeyPhrase:(BOOL)animated;
@end @end
@@ -34,8 +37,10 @@
+ (void)initialize { + (void)initialize {
[MPConfig get];
#ifdef DEBUG #ifdef DEBUG
[Logger get].autoprintLevel = LogLevelTrace; [PearlLogger get].autoprintLevel = PearlLogLevelTrace;
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)]; [NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
#endif #endif
} }
@@ -44,7 +49,7 @@
static NSDictionary *MPKeyPhraseQuery = nil; static NSDictionary *MPKeyPhraseQuery = nil;
if (!MPKeyPhraseQuery) if (!MPKeyPhraseQuery)
MPKeyPhraseQuery = [KeyChain createQueryForClass:kSecClassGenericPassword MPKeyPhraseQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObject:@"MasterPassword" attributes:[NSDictionary dictionaryWithObject:@"MasterPassword"
forKey:(__bridge id)kSecAttrService] forKey:(__bridge id)kSecAttrService]
matches:nil]; matches:nil];
@@ -56,7 +61,7 @@
static NSDictionary *MPKeyPhraseHashQuery = nil; static NSDictionary *MPKeyPhraseHashQuery = nil;
if (!MPKeyPhraseHashQuery) if (!MPKeyPhraseHashQuery)
MPKeyPhraseHashQuery = [KeyChain createQueryForClass:kSecClassGenericPassword MPKeyPhraseHashQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObject:@"MasterPasswordHash" attributes:[NSDictionary dictionaryWithObject:@"MasterPasswordHash"
forKey:(__bridge id)kSecAttrService] forKey:(__bridge id)kSecAttrService]
matches:nil]; matches:nil];
@@ -67,11 +72,23 @@
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#ifndef PRODUCTION #ifndef PRODUCTION
[TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"]; @try {
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys: [TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"];
[NSNumber numberWithBool:YES], @"logToConsole", [TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
nil]]; [NSNumber numberWithBool:NO], @"logToConsole",
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched]; [NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelInfo)
TFLog(@"%@", message);
return YES;
}];
}
@catch (NSException *exception) {
err(@"TestFlight: %@", exception);
}
#endif #endif
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)]; UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
@@ -131,21 +148,37 @@
if ([NSStringFromSelector(@selector(storeKeyPhrase)) if ([NSStringFromSelector(@selector(storeKeyPhrase))
isEqualToString:[note.object description]]) { isEqualToString:[note.object description]]) {
self.keyPhrase = self.keyPhrase; self.keyPhrase = self.keyPhrase;
[self loadKeyPhrase]; [self loadKeyPhrase:YES];
} }
if ([NSStringFromSelector(@selector(forgetKeyPhrase)) if ([NSStringFromSelector(@selector(forgetKeyPhrase))
isEqualToString:[note.object description]]) isEqualToString:[note.object description]])
[self loadKeyPhrase]; [self loadKeyPhrase:YES];
}]; }];
#ifndef PRODUCTION
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
@"Thank you for taking the time to test Master Password.\n\n"
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
@"Contact me directly at:\n"
@"lhunath@lyndir.com\n"
@"Or report detailed issues at:\n"
@"https://youtrack.lyndir.com\n"
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#endif
[[UIApplication sharedApplication] setStatusBarHidden:NO
withAnimation:UIStatusBarAnimationSlide];
return [super application:application didFinishLaunchingWithOptions:launchOptions]; return [super application:application didFinishLaunchingWithOptions:launchOptions];
} }
- (void)applicationDidBecomeActive:(UIApplication *)application { - (void)applicationDidBecomeActive:(UIApplication *)application {
if ([[MPConfig get].showQuickStart boolValue]) if ([[MPConfig get].showQuickStart boolValue])
[self showGuide]; [self showGuide];
else else
[self loadKeyPhrase]; [self loadKeyPhrase:NO];
#ifndef PRODUCTION #ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated]; [TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
@@ -161,18 +194,16 @@
#endif #endif
} }
- (void)loadKeyPhrase { - (void)loadKeyPhrase:(BOOL)animated {
if ([[MPConfig get].forgetKeyPhrase boolValue]) { if (self.keyPhrase)
[self forgetKeyPhrase];
return; return;
}
[self loadStoredKeyPhrase]; [self loadStoredKeyPhrase];
if (!self.keyPhrase) { if (!self.keyPhrase) {
// Key phrase is not known. Ask user to set/specify it. // Key phrase is not known. Ask user to set/specify it.
dbg(@"Key phrase not known. Will ask user."); dbg(@"Key phrase not known. Will ask user.");
[self askKeyPhrase]; [self askKeyPhrase:animated];
return; return;
} }
} }
@@ -180,22 +211,28 @@
- (void)forgetKeyPhrase { - (void)forgetKeyPhrase {
dbg(@"Forgetting key phrase."); dbg(@"Forgetting key phrase.");
[AlertViewController showAlertWithTitle:@"Changing Master Password" [PearlAlert showAlertWithTitle:@"Changing Master Password"
message: message:
@"You've requested to change your master password.\n\n" @"This will allow you to log in with a different master password.\n\n"
@"If you continue, your current sites and passwords will become unavailable.\n\n" @"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"You can always change back to the old master password later.\n" @"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"Your old sites and passwords will then become available again." @"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex != [alert cancelButtonIndex]) { if (buttonIndex != [alert cancelButtonIndex]) {
// Key phrase reset. Delete it. // Key phrase reset. Delete it.
dbg(@"Deleting master key phrase and hash from key chain."); dbg(@"Deleting master key phrase and hash from key chain.");
[KeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]]; [PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]];
[KeyChain deleteItemForQuery:[MPAppDelegate keyPhraseHashQuery]]; [PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseHashQuery]];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
#endif
} }
[self loadKeyPhrase]; [self loadKeyPhrase:YES];
#ifndef PRODUCTION #ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
@@ -203,7 +240,12 @@
} }
cancelTitle:[PearlStrings get].commonButtonAbort cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil]; otherTitles:[PearlStrings get].commonButtonContinue, nil];
[MPConfig get].forgetKeyPhrase = [NSNumber numberWithBool:NO]; }
- (void)signOut {
self.keyPhrase = nil;
[self loadKeyPhrase:YES];
} }
- (void)loadStoredKeyPhrase { - (void)loadStoredKeyPhrase {
@@ -211,77 +253,24 @@
if ([[MPConfig get].storeKeyPhrase boolValue]) { if ([[MPConfig get].storeKeyPhrase boolValue]) {
// Key phrase is stored in keychain. Load it. // Key phrase is stored in keychain. Load it.
dbg(@"Loading master key phrase from key chain."); dbg(@"Loading master key phrase from key chain.");
NSData *keyPhraseData = [KeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseQuery]]; self.keyPhrase = [PearlKeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseQuery]];
dbg(@" -> Master key phrase %@.", keyPhraseData? @"found": @"NOT found"); dbg(@" -> Master key phrase %@.", self.keyPhrase? @"found": @"NOT found");
self.keyPhrase = keyPhraseData? [[NSString alloc] initWithBytes:keyPhraseData.bytes length:keyPhraseData.length
encoding:NSUTF8StringEncoding]: nil;
} else { } else {
// Key phrase should not be stored in keychain. Delete it. // Key phrase should not be stored in keychain. Delete it.
dbg(@"Deleting master key phrase from key chain."); dbg(@"Deleting master key phrase from key chain.");
[KeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]]; [PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]];
#ifndef PRODUCTION #ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
#endif #endif
} }
} }
- (void)askKeyPhrase { - (void)askKeyPhrase:(BOOL)animated {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
NSData *keyPhraseHash = [KeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseHashQuery]]; [self.navigationController presentViewController:
dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known"); [self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil];
[AlertViewController showAlertWithTitle:@"Master Password"
message:keyPhraseHash? @"Unlock with your master password:": @"Choose your master password:"
viewStyle:UIAlertViewStyleSecureTextInput
tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
exit(0);
NSString *answer = [alert textFieldAtIndex:0].text;
if (![answer length]) {
// User didn't enter a key phrase.
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
message:@"No master password entered."
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) {
exit(0);
} cancelTitle:@"Quit" otherTitles:nil];
}
NSData *answerHash = [answer hashWith:PearlDigestSHA512];
if (keyPhraseHash)
// A key phrase hash is known -> a key phrase is set.
// Make sure the user's entered key phrase matches it.
if (![keyPhraseHash isEqual:answerHash]) {
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyPhraseHash, answerHash);
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
message:
@"Incorrect master password.\n\n"
@"If you are trying to use the app with a different master password, "
@"flip the 'Change my password' option in Settings."
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) {
exit(0);
} cancelTitle:@"Quit" otherTitles:nil];
return;
}
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
#endif
self.keyPhrase = answer;
} cancelTitle:@"Quit" otherTitles:@"Unlock", nil];
}); });
} }
@@ -330,25 +319,60 @@
}]; }];
} }
- (void)setKeyPhrase:(NSString *)keyPhrase { - (BOOL)tryMasterPassword:(NSString *)tryPassword {
NSData *keyPhraseHash = [PearlKeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseHashQuery]];
dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known");
if (![tryPassword length])
return NO;
NSData *tryKeyPhrase = keyPhraseForPassword(tryPassword);
NSData *tryKeyPhraseHash = keyPhraseHashForKeyPhrase(tryKeyPhrase);
if (keyPhraseHash)
// A key phrase hash is known -> a key phrase is set.
// Make sure the user's entered key phrase matches it.
if (![keyPhraseHash isEqual:tryKeyPhraseHash]) {
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyPhraseHash, tryKeyPhraseHash);
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif
return NO;
}
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
#endif
self.keyPhrase = tryKeyPhrase;
return YES;
}
- (void)setKeyPhrase:(NSData *)keyPhrase {
_keyPhrase = keyPhrase; _keyPhrase = keyPhrase;
if (keyPhrase)
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
else
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
if (keyPhrase) { if (keyPhrase) {
self.keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512]; self.keyPhraseHash = keyPhraseHashForKeyPhrase(keyPhrase);
self.keyPhraseHashHex = [self.keyPhraseHash encodeHex]; self.keyPhraseHashHex = [self.keyPhraseHash encodeHex];
dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex); dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex);
[KeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseHashQuery] [PearlKeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseHashQuery]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.keyPhraseHash, (__bridge id)kSecValueData, self.keyPhraseHash, (__bridge id)kSecValueData,
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
nil]]; nil]];
if ([[MPConfig get].storeKeyPhrase boolValue]) { if ([[MPConfig get].storeKeyPhrase boolValue]) {
dbg(@"Storing master key phrase in key chain."); dbg(@"Storing master key phrase in key chain.");
[KeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseQuery] [PearlKeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseQuery]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
[keyPhrase dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecValueData, keyPhrase, (__bridge id)kSecValueData,
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
nil]]; nil]];
} }
@@ -359,6 +383,11 @@
} }
} }
- (NSData *)keyPhraseWithLength:(NSUInteger)keyLength {
return [self.keyPhrase subdataWithRange:NSMakeRange(0, MIN(keyLength, self.keyPhrase.length))];
}
#pragma mark - Core Data stack #pragma mark - Core Data stack
/** /**

View File

@@ -6,12 +6,11 @@
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@interface MPConfig : Config @interface MPConfig : PearlConfig
@property (nonatomic, retain) NSNumber *dataStoreError; @property (nonatomic, retain) NSNumber *dataStoreError;
@property (nonatomic, retain) NSNumber *storeKeyPhrase; @property (nonatomic, retain) NSNumber *storeKeyPhrase;
@property (nonatomic, retain) NSNumber *rememberKeyPhrase; @property (nonatomic, retain) NSNumber *rememberKeyPhrase;
@property (nonatomic, retain) NSNumber *forgetKeyPhrase;
@property (nonatomic, retain) NSNumber *helpHidden; @property (nonatomic, retain) NSNumber *helpHidden;
@property (nonatomic, retain) NSNumber *showQuickStart; @property (nonatomic, retain) NSNumber *showQuickStart;

View File

@@ -9,9 +9,7 @@
#import "MPConfig.h" #import "MPConfig.h"
@implementation MPConfig @implementation MPConfig
@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, helpHidden, showQuickStart;
@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, forgetKeyPhrase, helpHidden, showQuickStart;
- (id)init { - (id)init {
@@ -22,7 +20,6 @@
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKeyPhrase)), [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKeyPhrase)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(forgetKeyPhrase)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)), [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
nil]]; nil]];

View File

@@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity @interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, assign) int16_t counter; @property (nonatomic, assign) uint16_t counter;
@end @end

View File

@@ -21,7 +21,7 @@
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name { + (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
return [KeyChain createQueryForClass:kSecClassGenericPassword return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"DevicePrivate", (__bridge id)kSecAttrService, @"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount, name, (__bridge id)kSecAttrAccount,
@@ -35,24 +35,22 @@
NSData *encryptedContent; NSData *encryptedContent;
if (self.type == MPElementTypeStoredDevicePrivate) if (self.type == MPElementTypeStoredDevicePrivate)
encryptedContent = [KeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]]; encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else else
encryptedContent = self.contentObject; encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get].keyPhrase NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyPhraseWithLength:PearlCryptKeySize]
dataUsingEncoding:NSUTF8StringEncoding]
usePadding:YES]; usePadding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding]; return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
} }
- (void)setContent:(id)content { - (void)setContent:(id)content {
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get].keyPhrase NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyPhraseWithLength:PearlCryptKeySize]
dataUsingEncoding:NSUTF8StringEncoding]
usePadding:YES]; usePadding:YES];
if (self.type == MPElementTypeStoredDevicePrivate) { if (self.type == MPElementTypeStoredDevicePrivate) {
[KeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name] [PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData, encryptedContent, (__bridge id)kSecValueData,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,

View File

@@ -21,7 +21,7 @@
[super viewDidLoad]; [super viewDidLoad];
[UIUtils autoSizeContent:self.scrollView]; [PearlUIUtils autoSizeContent:self.scrollView];
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
@@ -29,9 +29,16 @@
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
[MPConfig get].showQuickStart = [NSNumber numberWithBool:NO]; [MPConfig get].showQuickStart = [NSNumber numberWithBool:NO];
[[MPAppDelegate get] loadKeyPhrase];
} }
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[MPAppDelegate get] loadKeyPhrase:animated];
}
- (void)viewDidUnload { - (void)viewDidUnload {
[self setScrollView:nil]; [self setScrollView:nil];

View File

@@ -11,7 +11,7 @@
#import "MPSearchDelegate.h" #import "MPSearchDelegate.h"
#import "IASKAppSettingsViewController.h" #import "IASKAppSettingsViewController.h"
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, UISearchBarDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate> @interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate>
@property (strong, nonatomic) MPElementEntity *activeElement; @property (strong, nonatomic) MPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController; @property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;

View File

@@ -114,7 +114,8 @@
} }
}]; }];
[self closeAlert]; self.alertBody.text = nil;
self.contentTipEditIcon.alpha = 0;
[super viewDidLoad]; [super viewDidLoad];
} }
@@ -146,11 +147,11 @@
[[MPAppDelegate get] saveContext]; [[MPAppDelegate get] saveContext];
if (animated) if (animated)
[UIView animateWithDuration:0.2 animations:^{ [UIView animateWithDuration:0.3f animations:^{
[self updateWasAnimated:YES]; [self updateWasAnimated:animated];
}]; }];
else else
[self updateWasAnimated:NO]; [self updateWasAnimated:animated];
} }
- (void)updateWasAnimated:(BOOL)animated { - (void)updateWasAnimated:(BOOL)animated {
@@ -169,7 +170,7 @@
self.contentField.enabled = NO; self.contentField.enabled = NO;
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
self.passwordCounter.text = [NSString stringWithFormat:@"%d", ((MPElementGeneratedEntity *) self.activeElement).counter]; self.passwordCounter.text = [NSString stringWithFormat:@"%u", ((MPElementGeneratedEntity *) self.activeElement).counter];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = self.activeElement.description; NSString *description = self.activeElement.description;
@@ -196,7 +197,7 @@
if (hidden) { if (hidden) {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 373); self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 373);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 415); self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 416);
[MPConfig get].helpHidden = [NSNumber numberWithBool:YES]; [MPConfig get].helpHidden = [NSNumber numberWithBool:YES];
} else { } else {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175); self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
@@ -216,8 +217,11 @@
[NSURLRequest requestWithURL: [NSURLRequest requestWithURL:
[NSURL URLWithString:[NSString stringWithFormat:@"#%@", chapter] relativeToURL: [NSURL URLWithString:[NSString stringWithFormat:@"#%@", chapter] relativeToURL:
[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]]; [[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]];
[self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
ClassNameFromMPElementType(self.activeElement.type)]]; NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
ClassNameFromMPElementType(self.activeElement.type)]];
if (error.length)
err(@"setClass: %@", error);
} }
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon { - (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
@@ -228,11 +232,6 @@
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.2f animations:^{
self.contentTipContainer.alpha = 1; self.contentTipContainer.alpha = 1;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (!finished) {
icon.hidden = YES;
return;
}
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5.0f * NSEC_PER_SEC); dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5.0f * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.2f animations:^{
@@ -300,7 +299,7 @@
[self updateAnimated:YES]; [self updateAnimated:YES];
// Show new and old password. // Show new and old password.
if (oldPassword && ![oldPassword isEqualToString:newPassword]) if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
[self showAlertWithTitle:@"Password Changed!" message:l(@"The password for %@ has changed.\n\n" [self showAlertWithTitle:@"Password Changed!" message:l(@"The password for %@ has changed.\n\n"
@"Don't forget to update the site with your new password! " @"Don't forget to update the site with your new password! "
@"Your old password was:\n" @"Your old password was:\n"
@@ -334,7 +333,7 @@
- (IBAction)action:(id)sender { - (IBAction)action:(id)sender {
[SheetViewController showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic [PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex]) if (buttonIndex == [sheet cancelButtonIndex])
return; return;
@@ -355,12 +354,18 @@
settingsVC.delegate = self; settingsVC.delegate = self;
[self.navigationController pushViewController:settingsVC animated:YES]; [self.navigationController pushViewController:settingsVC animated:YES];
break; break;
} }
#ifndef PRODUCTION #ifndef PRODUCTION
case 4: case 4:
[TestFlight openFeedbackView]; [TestFlight openFeedbackView];
break; break;
case 5: {
#else
case 4: {
#endif #endif
[[MPAppDelegate get] signOut];
break;
}
} }
#ifndef PRODUCTION #ifndef PRODUCTION
@@ -372,9 +377,15 @@
#ifndef PRODUCTION #ifndef PRODUCTION
@"Feedback", @"Feedback",
#endif #endif
@"Sign Out",
nil]; nil];
} }
- (MPElementType)selectedType {
return self.activeElement.type;
}
- (void)didSelectType:(MPElementType)type { - (void)didSelectType:(MPElementType)type {
[self updateElement:^{ [self updateElement:^{
@@ -383,7 +394,7 @@
// Type requires a different class of element. Recreate the element. // Type requires a different class of element. Recreate the element.
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{ [[MPAppDelegate managedObjectContext] performBlockAndWait:^{
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:[MPAppDelegate managedObjectContext]]; inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name; newElement.name = self.activeElement.name;
newElement.mpHashHex = self.activeElement.mpHashHex; newElement.mpHashHex = self.activeElement.mpHashHex;
newElement.uses = self.activeElement.uses; newElement.uses = self.activeElement.uses;
@@ -406,24 +417,17 @@
- (void)didSelectElement:(MPElementEntity *)element { - (void)didSelectElement:(MPElementEntity *)element {
self.activeElement = element; if (element) {
[self.activeElement use]; self.activeElement = element;
[self.activeElement use];
[self.searchDisplayController setActive:NO animated:YES]; [self.searchDisplayController setActive:NO animated:YES];
self.searchDisplayController.searchBar.text = self.activeElement.name; self.searchDisplayController.searchBar.text = self.activeElement.name;
#ifndef PRODUCTION #ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement]; [TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement];
#endif
[self updateAnimated:YES];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
#endif #endif
}
[self updateAnimated:YES]; [self updateAnimated:YES];
} }

View File

@@ -15,9 +15,11 @@
@end @end
@interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate> @interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController; @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSString *query;
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate; @property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController; @property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;

View File

@@ -18,40 +18,85 @@
@end @end
@implementation MPSearchDelegate @implementation MPSearchDelegate
@synthesize query;
@synthesize dateFormatter;
@synthesize fetchedResultsController; @synthesize fetchedResultsController;
@synthesize delegate; @synthesize delegate;
@synthesize searchDisplayController; @synthesize searchDisplayController;
@synthesize searchTipContainer; @synthesize searchTipContainer;
- (id)init {
if (!([super init]))
return nil;
self.dateFormatter = [NSDateFormatter new];
self.dateFormatter.timeStyle = NSDateFormatterNoStyle;
self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
self.query = @"";
return self;
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
for (NSUInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
NSUInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
if (!rowCount)
continue;
if (rowCount == 1)
[self tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
break;
}
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
#endif
[self.delegate didSelectElement:nil];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchBar.searchResultsButtonSelected && !searchText.length)
searchBar.text = @" ";
self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (!self.query)
self.query = @"";
}
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller { - (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
self.searchDisplayController.searchBar.text = @""; controller.searchBar.prompt = @"Enter the site's name (eg. apple.com):";
self.searchDisplayController.searchBar.prompt = @"Enter the site's domain name (eg. apple.com):";
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.2f animations:^{
self.searchTipContainer.alpha = 0; self.searchTipContainer.alpha = 0;
}]; }];
}
[self update]; - (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
self.query = @"";
} }
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller { - (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
self.searchDisplayController.searchBar.prompt = nil; controller.searchBar.prompt = nil;
controller.searchBar.searchResultsButtonSelected = NO;
} }
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView { - (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
[[NSNotificationCenter defaultCenter] addObserverForName:UIScreenModeDidChangeNotification
object:nil queue:nil usingBlock:^(NSNotification *note) {
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
err(@"Couldn't fetch elements: %@", error);
}];
tableView.backgroundColor = [UIColor blackColor]; tableView.backgroundColor = [UIColor blackColor];
tableView.rowHeight = 34.0f;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone; tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView.rowHeight = 48.0f;
} }
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
@@ -63,19 +108,18 @@
- (void)update { - (void)update {
NSString *query = self.searchDisplayController.searchBar.text; assert(self.query);
if (!query) assert([MPAppDelegate get].keyPhraseHashHex);
query = @"";
NSFetchRequest *fetchRequest = [[MPAppDelegate get].managedObjectModel NSFetchRequest *fetchRequest = [[MPAppDelegate get].managedObjectModel
fetchRequestFromTemplateWithName:@"MPSearchElement" fetchRequestFromTemplateWithName:@"MPElements"
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys: substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
query, @"query", self.query, @"query",
[MPAppDelegate get].keyPhraseHashHex, @"mpHashHex", [MPAppDelegate get].keyPhraseHashHex, @"mpHashHex",
nil]]; nil]];
[fetchRequest setSortDescriptors: [fetchRequest setSortDescriptors:
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]]; [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
self.fetchedResultsController.delegate = nil;
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext] managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil]; sectionNameKeyPath:nil cacheName:nil];
@@ -107,8 +151,7 @@
case NSFetchedResultsChangeUpdate: case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] [self configureCell:[tableView cellForRowAtIndexPath:indexPath]
inTableView:tableView inTableView:tableView atIndexPath:indexPath];
atIndexPath:indexPath];
break; break;
case NSFetchedResultsChangeMove: case NSFetchedResultsChangeMove:
@@ -121,7 +164,7 @@
} }
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
UITableView *tableView = self.searchDisplayController.searchResultsTableView; UITableView *tableView = self.searchDisplayController.searchResultsTableView;
@@ -146,32 +189,38 @@
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.fetchedResultsController sections] count] + 1; return [[self.fetchedResultsController sections] count] + ([self.query length]? 1: 0);
} }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == [self numberOfSectionsInTableView:tableView] - 1) if (section < [[self.fetchedResultsController sections] count])
return 1; return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects]; return 1;
} }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"]; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"];
if (!cell) { if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"MPElementSearch"]; cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"ui_list_middle"] resizableImageWithCapInsets:UIEdgeInsetsMake(5, 5, 5, 5)]];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]];
backgroundImageView.frame = CGRectMake(-5, 0, 330, 34); backgroundImageView.frame = CGRectMake(-5, 0, 330, 34);
UIView *backgroundView = [[UIView alloc] initWithFrame:cell.frame]; backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f);
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)];
[backgroundView addSubview:backgroundImageView]; [backgroundView addSubview:backgroundImageView];
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.backgroundView = backgroundView; cell.backgroundView = backgroundView;
cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor]; cell.textLabel.textColor = [UIColor whiteColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor]; cell.detailTextLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor = [UIColor lightGrayColor]; cell.detailTextLabel.textColor = [UIColor lightGrayColor];
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.clipsToBounds = YES;
} }
[self configureCell:cell inTableView:tableView atIndexPath:indexPath]; [self configureCell:cell inTableView:tableView atIndexPath:indexPath];
@@ -185,11 +234,12 @@
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = element.name; cell.textLabel.text = element.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", element.uses]; cell.detailTextLabel.text = [NSString stringWithFormat:@"Used %d times, last on %@",
element.uses, [self.dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:element.lastUsed]]];
} else { } else {
// "New" section // "New" section
cell.textLabel.text = self.searchDisplayController.searchBar.text; cell.textLabel.text = self.query;
cell.detailTextLabel.text = @"New"; cell.detailTextLabel.text = @"Create a new site.";
} }
} }
@@ -200,11 +250,13 @@
else { else {
// "New" section. // "New" section.
NSString *siteName = self.searchDisplayController.searchBar.text; NSString *siteName = self.query;
[AlertViewController showAlertWithTitle:@"New Site" [PearlAlert showAlertWithTitle:@"New Site"
message:l(@"Do you want to create a new site named:\n%@", siteName) message:l(@"Do you want to create a new site named:\n%@", siteName)
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
@@ -226,10 +278,10 @@
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (--section == -1) if (section < [[self.fetchedResultsController sections] count])
return @""; return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
return [[[self.fetchedResultsController sections] objectAtIndex:section] name]; return @"";
} }
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
@@ -244,19 +296,17 @@
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == [[self.fetchedResultsController sections] count]) if (indexPath.section < [[self.fetchedResultsController sections] count]) {
// "New" section. if (editingStyle == UITableViewCellEditingStyleDelete)
return; [self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (editingStyle == UITableViewCellEditingStyleDelete) [self.fetchedResultsController.managedObjectContext deleteObject:element];
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.fetchedResultsController.managedObjectContext deleteObject:element];
#ifndef PRODUCTION #ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement]; [TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement];
#endif #endif
}]; }];
}
} }

View File

@@ -12,6 +12,9 @@
- (void)didSelectType:(MPElementType)type; - (void)didSelectType:(MPElementType)type;
@optional
- (MPElementType)selectedType;
@end @end
@interface MPTypeViewController : UITableViewController @interface MPTypeViewController : UITableViewController

View File

@@ -8,6 +8,13 @@
#import "MPTypeViewController.h" #import "MPTypeViewController.h"
@interface MPTypeViewController ()
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath;
@end
@implementation MPTypeViewController @implementation MPTypeViewController
@synthesize delegate; @synthesize delegate;
@@ -25,30 +32,51 @@
return YES; return YES;
} }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if ([delegate respondsToSelector:@selector(selectedType)])
if ([delegate selectedType] == [self typeAtIndexPath:indexPath])
[cell iterateSubviewsContinueAfter:^BOOL(UIView *subview) {
if ([subview isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = ((UIImageView *)subview);
if (!imageView.highlightedImage)
imageView.highlightedImage = [imageView.image highlightedImage];
imageView.highlighted = YES;
return NO;
}
return YES;
}];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
assert(self.navigationController.topViewController == self); assert(self.navigationController.topViewController == self);
MPElementType type; [delegate didSelectType:[self typeAtIndexPath:indexPath]];
[self.navigationController popViewControllerAnimated:YES];
}
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) { switch (indexPath.section) {
case 0: { case 0: {
// Calculated // Calculated
switch (indexPath.row) { switch (indexPath.row) {
case 0: case 0:
type = MPElementTypeCalculatedLong; return MPElementTypeCalculatedLong;
break;
case 1: case 1:
type = MPElementTypeCalculatedMedium; return MPElementTypeCalculatedMedium;
break;
case 2: case 2:
type = MPElementTypeCalculatedShort; return MPElementTypeCalculatedShort;
break;
case 3: case 3:
type = MPElementTypeCalculatedBasic; return MPElementTypeCalculatedBasic;
break;
case 4: case 4:
type = MPElementTypeCalculatedPIN; return MPElementTypeCalculatedPIN;
break;
default: default:
[NSException raise:NSInternalInconsistencyException [NSException raise:NSInternalInconsistencyException
@@ -61,11 +89,9 @@
// Stored // Stored
switch (indexPath.row) { switch (indexPath.row) {
case 0: case 0:
type = MPElementTypeStoredPersonal; return MPElementTypeStoredPersonal;
break;
case 1: case 1:
type = MPElementTypeStoredDevicePrivate; return MPElementTypeStoredDevicePrivate;
break;
default: default:
[NSException raise:NSInternalInconsistencyException [NSException raise:NSInternalInconsistencyException
@@ -79,8 +105,7 @@
format:@"Unsupported section: %d, when selecting element type.", indexPath.section]; format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
} }
[delegate didSelectType:type]; @throw nil;
[self.navigationController popViewControllerAnimated:YES];
} }
@end @end

View File

@@ -33,31 +33,39 @@ typedef enum {
} MPElementType; } MPElementType;
#ifndef PRODUCTION #ifndef PRODUCTION
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction" #define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@" #define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard" #define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter" #define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword" #define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert" #define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
#define MPTestFlightCheckpointSelectType @"MPTestFlightCheckpointSelectType_%@" #define MPTestFlightCheckpointSelectType @"MPTestFlightCheckpointSelectType_%@"
#define MPTestFlightCheckpointSelectElement @"MPTestFlightCheckpointSelectElement" #define MPTestFlightCheckpointSelectElement @"MPTestFlightCheckpointSelectElement"
#define MPTestFlightCheckpointDeleteElement @"MPTestFlightCheckpointDeleteElement" #define MPTestFlightCheckpointDeleteElement @"MPTestFlightCheckpointDeleteElement"
#define MPTestFlightCheckpointCancelSearch @"MPTestFlightCheckpointCancelSearch" #define MPTestFlightCheckpointCancelSearch @"MPTestFlightCheckpointCancelSearch"
#define MPTestFlightCheckpointExternalLink @"MPTestFlightCheckpointExternalLink" #define MPTestFlightCheckpointExternalLink @"MPTestFlightCheckpointExternalLink"
#define MPTestFlightCheckpointLaunched @"MPTestFlightCheckpointLaunched" #define MPTestFlightCheckpointLaunched @"MPTestFlightCheckpointLaunched"
#define MPTestFlightCheckpointActivated @"MPTestFlightCheckpointActivated" #define MPTestFlightCheckpointActivated @"MPTestFlightCheckpointActivated"
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated" #define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated" #define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide" #define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged" #define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored" #define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch" #define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
#define MPTestFlightCheckpointMPAsked @"MPTestFlightCheckpointMPAsked" #define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
#define MPTestFlightCheckpointStoreIncompatible @"MPTestFlightCheckpointStoreIncompatible" #define MPTestFlightCheckpointMPAsked @"MPTestFlightCheckpointMPAsked"
#define MPTestFlightCheckpointSetKeyphraseLength @"MPTestFlightCheckpointSetKeyphraseLength_%d" #define MPTestFlightCheckpointStoreIncompatible @"MPTestFlightCheckpointStoreIncompatible"
#define MPTestFlightCheckpointSetKeyphraseLength @"MPTestFlightCheckpointSetKeyphraseLength_%d"
#endif #endif
#define MPNotificationKeySet @"MPNotificationKeySet"
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
NSData *keyPhraseForPassword(NSString *password);
NSData *keyPhraseHashForPassword(NSString *password);
NSData *keyPhraseHashForKeyPhrase(NSData *keyPhrase);
NSString *NSStringFromMPElementType(MPElementType type); NSString *NSStringFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type); NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type); Class ClassFromMPElementType(MPElementType type);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSString *keyPhrase, int counter); NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *keyPhrase, uint16_t counter);

View File

@@ -10,7 +10,26 @@
#import "MPElementGeneratedEntity.h" #import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h" #import "MPElementStoredEntity.h"
#define MP_salt nil
#define MP_N 16384
#define MP_r 8
#define MP_p 1
#define MP_dkLen 64
#define MP_hash PearlDigestSHA256
NSData *keyPhraseForPassword(NSString *password) {
return [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
}
NSData *keyPhraseHashForPassword(NSString *password) {
return keyPhraseHashForKeyPhrase(keyPhraseForPassword(password));
}
NSData *keyPhraseHashForKeyPhrase(NSData *keyPhrase) {
return [keyPhrase hashWith:MP_hash];
}
NSString *NSStringFromMPElementType(MPElementType type) { NSString *NSStringFromMPElementType(MPElementType type) {
if (!type) if (!type)
@@ -81,7 +100,7 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
} }
static NSDictionary *MPTypes_ciphers = nil; static NSDictionary *MPTypes_ciphers = nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSString *keyPhrase, int counter) { NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *keyPhrase, uint16_t counter) {
assert(type & MPElementTypeClassCalculated); assert(type & MPElementTypeClassCalculated);
@@ -91,13 +110,18 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSString *keyPh
// Determine the hash whose bytes will be used for calculating a password: md4(name-keyPhrase) // Determine the hash whose bytes will be used for calculating a password: md4(name-keyPhrase)
assert(name && keyPhrase); assert(name && keyPhrase);
NSData *keyHash = [[NSString stringWithFormat:@"%@-%@-%d", name, keyPhrase, counter] hashWith:PearlDigestSHA1]; uint16_t ncounter = htons(counter);
NSData *keyHash = [[NSData dataByConcatenatingWithDelimitor:'-' datas:
[name dataUsingEncoding:NSUTF8StringEncoding],
keyPhrase,
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
nil] hashWith:PearlDigestSHA1];
const char *keyBytes = keyHash.bytes; const char *keyBytes = keyHash.bytes;
// Determine the cipher from the first hash byte. // Determine the cipher from the first hash byte.
assert([keyHash length]); assert([keyHash length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)] NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
valueForKey:NSStringFromMPElementType(type)]; valueForKey:NSStringFromMPElementType(type)];
NSString *cipher = [typeCiphers objectAtIndex:keyBytes[0] % [typeCiphers count]]; NSString *cipher = [typeCiphers objectAtIndex:keyBytes[0] % [typeCiphers count]];
// Encode the content, character by character, using subsequent hash bytes and the cipher. // Encode the content, character by character, using subsequent hash bytes and the cipher.

View File

@@ -0,0 +1,21 @@
//
// MBUnlockViewController.h
// MasterPassword
//
// Created by Maarten Billemont on 22/02/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface MPUnlockViewController : UIViewController <UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *lock;
@property (weak, nonatomic) IBOutlet UIImageView *spinner;
@property (weak, nonatomic) IBOutlet UITextField *field;
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
@property (weak, nonatomic) IBOutlet UIView *changeMPView;
- (IBAction)changeMP;
@end

View File

@@ -0,0 +1,205 @@
//
// MBUnlockViewController.m
// MasterPassword
//
// Created by Maarten Billemont on 22/02/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import "MPUnlockViewController.h"
#import "MPAppDelegate.h"
typedef enum {
MPLockscreenIdle,
MPLockscreenError,
MPLockscreenSuccess,
MPLockscreenProgress,
} MPLockscreen;
@interface MPUnlockViewController ()
@end
@implementation MPUnlockViewController
@synthesize lock;
@synthesize spinner;
@synthesize field;
@synthesize messageLabel;
@synthesize changeMPView;
- (void)showMessage:(NSString *)message state:(MPLockscreen)state {
__block void(^showMessageAnimation)(void) = ^{
self.lock.alpha = 0.0f;
switch (state) {
case MPLockscreenIdle:
[self.lock setImage:[UIImage imageNamed:@"lock_idle"]];
break;
case MPLockscreenError:
[self.lock setImage:[UIImage imageNamed:@"lock_red"]];
break;
case MPLockscreenSuccess:
[self.lock setImage:[UIImage imageNamed:@"lock_green"]];
break;
case MPLockscreenProgress:
[self.lock setImage:[UIImage imageNamed:@"lock_blue"]];
break;
}
self.lock.alpha = 0.0f;
[UIView animateWithDuration:1.0f animations:^{
self.lock.alpha = 1.0f;
} completion:^(BOOL finished) {
if (finished)
[UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
self.lock.alpha = 0.5f;
} completion:nil];
}];
[UIView animateWithDuration:0.5f animations:^{
self.messageLabel.alpha = 1.0f;
self.messageLabel.text = message;
}];
};
if (self.messageLabel.alpha)
[UIView animateWithDuration:0.3f animations:^{
self.messageLabel.alpha = 0.0f;
} completion:^(BOOL finished) {
if (finished)
showMessageAnimation();
}];
else
showMessageAnimation();
}
- (void)hideMessage {
[UIView animateWithDuration:0.5f animations:^{
self.messageLabel.alpha = 0.0f;
}];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)viewDidLoad {
self.messageLabel.text = nil;
self.messageLabel.alpha = 0;
self.changeMPView.alpha = 0;
self.spinner.alpha = 0;
self.field.text = nil;
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten
object:nil queue:nil usingBlock:^(NSNotification *note) {
[self.field becomeFirstResponder];
}];
[super viewDidLoad];
}
- (void)viewDidUnload {
[self setSpinner:nil];
[self setField:nil];
[self setMessageLabel:nil];
[self setLock:nil];
[self setChangeMPView:nil];
[super viewDidUnload];
}
- (void)viewWillAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:YES
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:NO
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewWillDisappear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[self.field becomeFirstResponder];
[super viewDidAppear:animated];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if ([textField.text length]) {
[textField resignFirstResponder];
return YES;
}
return NO;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@try {
dispatch_async(dispatch_get_main_queue(), ^{
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.toValue = [NSNumber numberWithFloat:2 * M_PI];
rotate.repeatCount = MAXFLOAT;
rotate.duration = 3.0;
[self.spinner.layer removeAllAnimations];
[self.spinner.layer addAnimation:rotate forKey:@"transform"];
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 1.0f;
}];
[self showMessage:@"Checking password..." state:MPLockscreenProgress];
});
if ([[MPAppDelegate get] tryMasterPassword:textField.text])
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Success!" state:MPLockscreenSuccess];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 1.5f), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
});
});
else
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Not valid." state:MPLockscreenError];
[UIView animateWithDuration:0.5f animations:^{
self.changeMPView.alpha = 1.0f;
}];
});
}
@finally {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 0.0f;
} completion:^(BOOL finished) {
[self.spinner.layer removeAllAnimations];
}];
});
}
});
}
- (IBAction)changeMP {
[[MPAppDelegate get] forgetKeyPhrase];
}
@end

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.0" toolsVersion="1938" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2177" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
<dependencies> <dependencies>
<development defaultVersion="4200" identifier="xcode"/> <development defaultVersion="4200" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="933"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1173"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Type View Controller - Type-->
<scene sceneID="WkW-SR-cr2"> <scene sceneID="WkW-SR-cr2">
<objects> <objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="jZj-N1-rhF" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="jZj-N1-rhF" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -52,9 +53,9 @@
<size key="shadowOffset" width="0.0" height="1"/> <size key="shadowOffset" width="0.0" height="1"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 14 characters, contains symbols." lineBreakMode="tailTruncation" minimumFontSize="10" id="6iu-aM-lJA"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 14 characters, contains symbols." lineBreakMode="tailTruncation" minimumFontSize="10" id="6iu-aM-lJA">
<rect key="frame" x="20" y="184" width="280" height="20"/> <rect key="frame" x="20" y="183" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/> <fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
@@ -96,9 +97,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<size key="shadowOffset" width="0.0" height="1"/> <size key="shadowOffset" width="0.0" height="1"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 8 characters, contains symbols." lineBreakMode="tailTruncation" minimumFontSize="10" id="MI2-JM-j4b"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 8 characters, contains symbols." lineBreakMode="tailTruncation" minimumFontSize="10" id="MI2-JM-j4b">
<rect key="frame" x="20" y="24" width="280" height="20"/> <rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/> <fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
@@ -129,9 +130,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<size key="shadowOffset" width="0.0" height="1"/> <size key="shadowOffset" width="0.0" height="1"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 4 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="Zi3-26-3iq"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 4 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="Zi3-26-3iq">
<rect key="frame" x="20" y="24" width="280" height="20"/> <rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/> <fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
@@ -162,9 +163,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<size key="shadowOffset" width="0.0" height="1"/> <size key="shadowOffset" width="0.0" height="1"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="8 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="CYQ-D8-vNS"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="8 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="CYQ-D8-vNS">
<rect key="frame" x="20" y="24" width="280" height="20"/> <rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/> <fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
@@ -195,9 +196,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<size key="shadowOffset" width="0.0" height="1"/> <size key="shadowOffset" width="0.0" height="1"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="4 numbers." lineBreakMode="tailTruncation" minimumFontSize="10" id="5Zm-AH-bAe"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="4 numbers." lineBreakMode="tailTruncation" minimumFontSize="10" id="5Zm-AH-bAe">
<rect key="frame" x="20" y="24" width="280" height="20"/> <rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/> <fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
@@ -251,9 +252,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<size key="shadowOffset" width="0.0" height="1"/> <size key="shadowOffset" width="0.0" height="1"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="AES-encrypted, iTunes backup, iCloud sync." lineBreakMode="tailTruncation" minimumFontSize="10" id="vNa-Yq-XIJ"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="AES-encrypted, iTunes backup, iCloud sync." lineBreakMode="tailTruncation" minimumFontSize="10" id="vNa-Yq-XIJ">
<rect key="frame" x="20" y="144" width="280" height="20"/> <rect key="frame" x="20" y="143" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/> <fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
@@ -284,9 +285,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<size key="shadowOffset" width="0.0" height="1"/> <size key="shadowOffset" width="0.0" height="1"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="AES-encrypted, stays on this device only." lineBreakMode="tailTruncation" minimumFontSize="10" id="6S8-9Y-pzj"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="AES-encrypted, stays on this device only." lineBreakMode="tailTruncation" minimumFontSize="10" id="6S8-9Y-pzj">
<rect key="frame" x="20" y="24" width="280" height="20"/> <rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/> <fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
@@ -308,6 +309,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
</objects> </objects>
<point key="canvasLocation" x="996" y="182"/> <point key="canvasLocation" x="996" y="182"/>
</scene> </scene>
<!--Main View Controller - Master Password-->
<scene sceneID="U26-Zf-euQ"> <scene sceneID="U26-Zf-euQ">
<objects> <objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="mK2-p1-3zC" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="mK2-p1-3zC" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -332,7 +334,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<rect key="frame" x="0.0" y="64" width="320" height="416"/> <rect key="frame" x="0.0" y="64" width="320" height="416"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="top" image="Content-Backdrop.png" id="0hY-LL-ITu"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="background.png" id="0hY-LL-ITu">
<rect key="frame" x="0.0" y="44" width="320" height="372"/> <rect key="frame" x="0.0" y="44" width="320" height="372"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView> </imageView>
@@ -340,10 +342,6 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<rect key="frame" x="0.0" y="43" width="320" height="175"/> <rect key="frame" x="0.0" y="43" width="320" height="175"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="top" image="Content-Backdrop.png" id="enb-OH-DVZ">
<rect key="frame" x="0.0" y="0.0" width="320" height="175"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<imageView userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleToFill" image="ui_panel_container.png" id="0Yh-Py-lB6"> <imageView userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleToFill" image="ui_panel_container.png" id="0Yh-Py-lB6">
<rect key="frame" x="11" y="20" width="298" height="87"/> <rect key="frame" x="11" y="20" width="298" height="87"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
@@ -373,7 +371,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="apple.com" lineBreakMode="tailTruncation" minimumFontSize="10" id="gSK-aB-wNI"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="apple.com" lineBreakMode="tailTruncation" minimumFontSize="10" id="gSK-aB-wNI">
<rect key="frame" x="25" y="20" width="270" height="26"/> <rect key="frame" x="25" y="20" width="270" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/> <fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="12"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/> <color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
@@ -383,7 +381,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<rect key="frame" x="20" y="46" width="280" height="60"/> <rect key="frame" x="20" y="46" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="textColor" red="0.47450980390000003" green="0.86666666670000003" blue="0.98431372549999996" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.47450980390000003" green="0.86666666670000003" blue="0.98431372549999996" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" name="Optima-ExtraBlack" family="Optima" pointSize="26"/> <fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="26"/>
<textInputTraits key="textInputTraits" autocorrectionType="no"/> <textInputTraits key="textInputTraits" autocorrectionType="no"/>
<connections> <connections>
<outlet property="delegate" destination="PQa-Xl-A3x" id="qOM-gq-c6g"/> <outlet property="delegate" destination="PQa-Xl-A3x" id="qOM-gq-c6g"/>
@@ -391,16 +389,16 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
</textField> </textField>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="Iuf-np-e9C"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="Iuf-np-e9C">
<rect key="frame" x="240" y="27" width="40" height="20"/> <rect key="frame" x="240" y="27" width="40" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Optima-ExtraBlack" family="Optima" pointSize="17"/> <fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/> <color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<size key="shadowOffset" width="0.0" height="1"/> <size key="shadowOffset" width="0.0" height="1"/>
</label> </label>
<button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt"> <button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt">
<rect key="frame" x="274.5" y="18.5" width="37" height="37"/> <rect key="frame" x="272.5" y="18.5" width="36.5" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/> <inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
<state key="normal" image="icon_plus.png"> <state key="normal" image="icon_plus.png">
@@ -415,7 +413,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
</connections> </connections>
</button> </button>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9FS-fS-xH6"> <button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9FS-fS-xH6">
<rect key="frame" x="275" y="18.5" width="37" height="37"/> <rect key="frame" x="273" y="18.5" width="36" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/> <inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
@@ -433,9 +431,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Cei-5z-uWE"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Cei-5z-uWE">
<rect key="frame" x="10" y="115" width="300" height="46"/> <rect key="frame" x="10" y="115" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/> <fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="15"/>
<size key="titleShadowOffset" width="0.0" height="1"/> <size key="titleShadowOffset" width="0.0" height="1"/>
<state key="normal" title="Long" backgroundImage="ui_button_standard_large.png"> <state key="normal" title="Long Password" backgroundImage="ui_button_standard_large.png">
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/> <color key="titleColor" cocoaTouchSystemColor="lightTextColor"/>
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/> <color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</state> </state>
@@ -447,22 +445,22 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
</connections> </connections>
</button> </button>
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" id="v2m-Gf-pEV"> <view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" id="v2m-Gf-pEV">
<rect key="frame" x="55" y="8" width="210" height="57"/> <rect key="frame" x="55" y="5" width="210" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="fdm-NG-GFC"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="fdm-NG-GFC">
<rect key="frame" x="0.0" y="-1" width="210" height="60"/> <rect key="frame" x="0.0" y="0.0" width="210" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Tap to set a password." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="ieN-QQ-PyR"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Tap to set a password." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="ieN-QQ-PyR">
<rect key="frame" x="20" y="0.0" width="170" height="37"/> <rect key="frame" x="20" y="0.0" width="170" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/> <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_edit.png" id="KEn-n3-qhX"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_edit.png" id="KEn-n3-qhX">
<rect key="frame" x="48.5" y="6.5" width="24" height="24"/> <rect key="frame" x="48" y="6" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -471,7 +469,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </view>
<view clipsSubviews="YES" alpha="0.60000000000000009" contentMode="scaleToFill" id="61G-By-qLB"> <view clipsSubviews="YES" contentMode="scaleToFill" id="61G-By-qLB">
<rect key="frame" x="0.0" y="216" width="320" height="200"/> <rect key="frame" x="0.0" y="216" width="320" height="200"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
@@ -536,34 +534,31 @@ L4m3P4sSw0rD</string>
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </view>
<searchBar contentMode="redraw" barStyle="blackOpaque" placeholder="Site" id="qeo-n2-WVh"> <searchBar contentMode="redraw" barStyle="blackOpaque" placeholder="Enter site name" showsSearchResultsButton="YES" id="qeo-n2-WVh">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/> <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<gestureRecognizers/> <gestureRecognizers/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/> <textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
<connections> <connections>
<outlet property="delegate" destination="PQa-Xl-A3x" id="oXp-0r-Gkl"/> <outlet property="delegate" destination="0QO-2P-OhD" id="Maj-Iz-6Hb"/>
</connections> </connections>
</searchBar> </searchBar>
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL"> <view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL">
<rect key="frame" x="55" y="-27" width="210" height="57"/> <rect key="frame" x="10" y="-25" width="300" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="ORD-Xv-bOQ"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="ORD-Xv-bOQ">
<rect key="frame" x="0.0" y="-1" width="210" height="60"/> <rect key="frame" x="0.0" y="0.0" width="300" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.15000000000000002" y="0.15000000000000002" width="0.69999999999999973" height="0.69999999999999973"/>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter the site's name." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="21b-bH-lR9"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Begin by entering the name of your site." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="21b-bH-lR9">
<rect key="frame" x="20" y="0.0" width="170" height="37"/> <rect key="frame" x="-20" y="9" width="340" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/> <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_edit.png" id="4Xf-mC-295">
<rect key="frame" x="49" y="6" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </view>
@@ -601,67 +596,61 @@ L4m3P4sSw0rD</string>
</objects> </objects>
<point key="canvasLocation" x="455" y="182"/> <point key="canvasLocation" x="455" y="182"/>
</scene> </scene>
<!--Guide View Controller-->
<scene sceneID="Rt1-b4-sUB"> <scene sceneID="Rt1-b4-sUB">
<objects> <objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="7yf-G7-kVy" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="7yf-G7-kVy" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController id="qz3-eG-aEi" customClass="MPGuideViewController" sceneMemberID="viewController"> <viewController id="qz3-eG-aEi" customClass="MPGuideViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="cwa-ct-2DY"> <scrollView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" directionalLockEnabled="YES" pagingEnabled="YES" showsVerticalScrollIndicator="NO" id="Nr4-hi-8b2">
<rect key="frame" x="0.0" y="20" width="320" height="460"/> <rect key="frame" x="0.0" y="20" width="320" height="460"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" pagingEnabled="YES" showsVerticalScrollIndicator="NO" id="Nr4-hi-8b2"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_0.png" id="eJP-cS-VRU">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/> <rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
<subviews> </imageView>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="guide_page_0.png" id="eJP-cS-VRU"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_1.png" id="1J9-z9-h96">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/> <rect key="frame" x="320" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="guide_page_1.png" id="1J9-z9-h96"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_2.png" id="8kf-Wm-F3L">
<rect key="frame" x="320" y="0.0" width="320" height="460"/> <rect key="frame" x="640" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="guide_page_2.png" id="8kf-Wm-F3L"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_3.png" id="H5W-P4-o7O">
<rect key="frame" x="640" y="0.0" width="320" height="460"/> <rect key="frame" x="960" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="guide_page_3.png" id="H5W-P4-o7O"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_4.png" id="IXz-T9-Umi">
<rect key="frame" x="960" y="0.0" width="320" height="460"/> <rect key="frame" x="1280" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="guide_page_4.png" id="IXz-T9-Umi"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_5.png" id="Hd0-Or-naq">
<rect key="frame" x="1280" y="0.0" width="320" height="460"/> <rect key="frame" x="1280" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="guide_page_5.png" id="Hd0-Or-naq"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_6.png" id="1jq-Ic-GVZ">
<rect key="frame" x="1280" y="0.0" width="320" height="460"/> <rect key="frame" x="1600" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="guide_page_6.png" id="1jq-Ic-GVZ"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="q93-J1-auj">
<rect key="frame" x="1600" y="0.0" width="320" height="460"/> <rect key="frame" x="1610" y="394" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</imageView> <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="q93-J1-auj"> <size key="titleShadowOffset" width="0.0" height="1"/>
<rect key="frame" x="1610" y="394" width="300" height="46"/> <state key="normal" title="Get started" backgroundImage="ui_button_green_large.png">
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/> <color key="titleShadowColor" red="0.194560533886145" green="0.30086678832116787" blue="0.1263640535337863" alpha="1" colorSpace="deviceRGB"/>
<size key="titleShadowOffset" width="0.0" height="1"/> </state>
<state key="normal" title="Get started" backgroundImage="ui_button_green_large.png"> <state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" red="0.194560533886145" green="0.30086678832116787" blue="0.1263640535337863" alpha="1" colorSpace="deviceRGB"/> </state>
</state> <connections>
<state key="highlighted"> <action selector="close" destination="qz3-eG-aEi" eventType="touchUpInside" id="uYR-EJ-U6H"/>
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/> </connections>
</state> </button>
<connections>
<action selector="close" destination="qz3-eG-aEi" eventType="touchUpInside" id="uYR-EJ-U6H"/>
</connections>
</button>
</subviews>
</scrollView>
</subviews> </subviews>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/> </scrollView>
</view>
<connections> <connections>
<outlet property="scrollView" destination="Nr4-hi-8b2" id="8QD-sv-vu6"/> <outlet property="scrollView" destination="Nr4-hi-8b2" id="8QD-sv-vu6"/>
</connections> </connections>
@@ -669,6 +658,113 @@ L4m3P4sSw0rD</string>
</objects> </objects>
<point key="canvasLocation" x="455" y="785"/> <point key="canvasLocation" x="455" y="785"/>
</scene> </scene>
<!--Unlock View Controller-->
<scene sceneID="HkH-JR-Fhy">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="OGA-5j-IcQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController storyboardIdentifier="MPUnlockViewController" modalTransitionStyle="flipHorizontal" id="Nbn-Rv-sP1" customClass="MPUnlockViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="PHH-XC-9QQ">
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="top" image="background.png" id="QWe-Gw-rD3">
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<view contentMode="scaleToFill" id="OP6-72-eij">
<rect key="frame" x="0.0" y="157" width="320" height="130"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="lock_idle.png" id="tyv-qJ-bKR">
<rect key="frame" x="110" y="15" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<imageView userInteractionEnabled="NO" alpha="0.0" contentMode="center" image="lock_idle.png" id="Lpf-KA-3CV">
<rect key="frame" x="110" y="15" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_spinner.png" id="27q-lX-0vy">
<rect key="frame" x="122" y="28" width="75" height="75"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Checking password..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="oU9-lf-nnJ">
<rect key="frame" x="20" y="115" width="280" height="15"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT">
<rect key="frame" x="20" y="89" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
</imageView>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o">
<rect key="frame" x="20" y="89" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="36"/>
<textInputTraits key="textInputTraits" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
</connections>
</textField>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
<rect key="frame" x="32" y="61" width="256" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" id="Wu7-Mg-9SD">
<rect key="frame" x="0.0" y="391" width="320" height="89"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Trying to log in with a different master password?" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="vnS-n6-NZI">
<rect key="frame" x="0.0" y="0.0" width="320" height="15"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="11"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="wad-V1-K3f">
<rect key="frame" x="10" y="23" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<size key="titleShadowOffset" width="0.0" height="1"/>
<state key="normal" title="Change master password" backgroundImage="ui_button_standard_large.png">
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/>
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="changeMP" destination="Nbn-Rv-sP1" eventType="touchUpInside" id="7RI-hu-SiS"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
<nil key="simulatedStatusBarMetrics"/>
<connections>
<outlet property="changeMPView" destination="Wu7-Mg-9SD" id="84H-HT-5ml"/>
<outlet property="field" destination="rTR-7Q-X8o" id="DHg-gg-MVD"/>
<outlet property="lock" destination="Lpf-KA-3CV" id="6cE-2g-4XQ"/>
<outlet property="messageLabel" destination="oU9-lf-nnJ" id="Ahc-hl-KnJ"/>
<outlet property="spinner" destination="27q-lX-0vy" id="jUx-GK-Lgf"/>
</connections>
</viewController>
</objects>
<point key="canvasLocation" x="455" y="1425"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="8r0-wA-Zre"> <scene sceneID="8r0-wA-Zre">
<objects> <objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -690,8 +786,8 @@ L4m3P4sSw0rD</string>
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="Content-Backdrop.png" width="480" height="480"/>
<image name="Square-bottom.png" width="551" height="58"/> <image name="Square-bottom.png" width="551" height="58"/>
<image name="background.png" width="480" height="480"/>
<image name="guide_page_0.png" width="320" height="480"/> <image name="guide_page_0.png" width="320" height="480"/>
<image name="guide_page_1.png" width="320" height="480"/> <image name="guide_page_1.png" width="320" height="480"/>
<image name="guide_page_2.png" width="320" height="480"/> <image name="guide_page_2.png" width="320" height="480"/>
@@ -702,6 +798,7 @@ L4m3P4sSw0rD</string>
<image name="icon_cancel.png" width="32" height="32"/> <image name="icon_cancel.png" width="32" height="32"/>
<image name="icon_edit.png" width="32" height="32"/> <image name="icon_edit.png" width="32" height="32"/>
<image name="icon_plus.png" width="32" height="32"/> <image name="icon_plus.png" width="32" height="32"/>
<image name="lock_idle.png" width="100" height="100"/>
<image name="tip_alert_black.png" width="235" height="81"/> <image name="tip_alert_black.png" width="235" height="81"/>
<image name="tip_basic_black.png" width="210" height="60"/> <image name="tip_basic_black.png" width="210" height="60"/>
<image name="ui_button_green_large.png" width="300" height="46"/> <image name="ui_button_green_large.png" width="300" height="46"/>
@@ -711,9 +808,69 @@ L4m3P4sSw0rD</string>
<image name="ui_list_middle.png" width="300" height="34"/> <image name="ui_list_middle.png" width="300" height="34"/>
<image name="ui_panel_container.png" width="300" height="87"/> <image name="ui_panel_container.png" width="300" height="87"/>
<image name="ui_panel_display.png" width="300" height="86"/> <image name="ui_panel_display.png" width="300" height="86"/>
<image name="ui_spinner.png" width="75" height="75"/>
<image name="ui_textfield.png" width="158" height="34"/>
</resources> </resources>
<classes>
<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="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"/>
</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"/>
</class>
<class className="MPUnlockViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPUnlockViewController.h"/>
<relationships>
<relationship kind="action" name="changeMP"/>
<relationship kind="outlet" name="changeMPView" candidateClass="UIView"/>
<relationship kind="outlet" name="field" candidateClass="UITextField"/>
<relationship kind="outlet" name="lock" candidateClass="UIImageView"/>
<relationship kind="outlet" name="messageLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="spinner" candidateClass="UIImageView"/>
</relationships>
</class>
</classes>
<simulatedMetricsContainer key="defaultSimulatedMetrics"> <simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar" statusBarStyle="blackTranslucent"/> <nil key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/> <simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination"/> <simulatedScreenMetrics key="destination"/>
</simulatedMetricsContainer> </simulatedMetricsContainer>

View File

@@ -56,6 +56,8 @@
<array> <array>
<string>armv7</string> <string>armv7</string>
</array> </array>
<key>UIStatusBarHidden</key>
<true/>
<key>UIStatusBarStyle</key> <key>UIStatusBarStyle</key>
<string>UIStatusBarStyleBlackOpaque</string> <string>UIStatusBarStyleBlackOpaque</string>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>

View File

@@ -18,13 +18,7 @@
#endif #endif
#endif #endif
#define PEARL #import "Pearl-Prefix.pch"
#define PEARL_CRYPTO
#define PEARL_UIKIT
#import "Pearl.h"
#import "Pearl-Crypto.h"
#import "Pearl-UIKit.h"
#import "MPTypes.h" #import "MPTypes.h"
#import "MPConfig.h" #import "MPConfig.h"

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="872" systemVersion="11C74" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic"> <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1170" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES"> <entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<attribute name="lastUsed" attributeType="Date" syncable="YES"/> <attribute name="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="mpHashHex" optional="YES" attributeType="String" syncable="YES"/> <attribute name="mpHashHex" optional="YES" attributeType="String" syncable="YES"/>
@@ -13,7 +13,7 @@
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES"> <entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/> <attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity> </entity>
<fetchRequest name="MPSearchElement" entity="MPElementEntity" predicateString="name BEGINSWITH[cd] $query AND mpHashHex == $mpHashHex"/> <fetchRequest name="MPElements" entity="MPElementEntity" predicateString="($query == '' OR name BEGINSWITH[cd] $query) AND mpHashHex == $mpHashHex"/>
<elements> <elements>
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/> <element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/> <element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 883 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 856 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 801 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 763 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Some files were not shown because too many files have changed in this diff Show More