2
0

Compare commits

..

41 Commits
1.2.0 ... 1.2.1

Author SHA1 Message Date
Maarten Billemont
2dba4c87ba Small build fixes.
[FIXED]     Linker warning about PIE.
[UPDATED]   Login failed is no longer recorded when login attempt was
            not using a password (noisy).
2012-06-27 15:01:58 +02:00
Maarten Billemont
6841ae2b0d Updated iTunesArtwork
[IMPROVED]  iTunesArtwork prettier & 1024px.
[ADDED]     FontReplacer target.
2012-06-27 13:53:39 +02:00
Maarten Billemont
a3698b9e47 Guide pager, fonts, crash.
[ADDED]     Page controller in guide to see where in the guide you are.
[ADDED]     FontReplacer to be able to "use" Exo from IB (by
            substituting "Futura").
[FIXED]     A crash when loading the main VC because of the reset
            password gesture recogniser.
[IMPROVED]  Font in tables to standard system fonts.
[IMPROVED]  Guide content positioning and sizing making space for pager.
2012-06-27 09:51:14 +02:00
Maarten Billemont
5b65c8c6bd Improve UI to be more HIG friendly.
[IMPROVED]  Use action sheets instead of alerts when showing destructive
            action choices.
[IMPROVED]  Make tappable regions at least 44x44pt.
[IMPROVED]  Title/message for alert/sheets.
2012-06-25 08:59:54 +02:00
Maarten Billemont
94c9d50a12 Ability to noop TestFlight and Crashlytics 2012-06-24 21:06:00 +02:00
Maarten Billemont
5849c9668f Format noise. 2012-06-24 18:27:10 +02:00
Maarten Billemont
4d4ba3425e Improvements to password import.
[FIXED]     Importing of mpsites with passwords showing for
            stored password types.
[FIXED]     Don't try to show mail composition dialog when the user has
            no mail account configured.  This will crash.  Instead,
            show a friendly popup explaining things.
[IMPROVED]  Message of password export emails.
[FIXED]     Hierarchy of MPUnlockVC so password field becomes touchable.
2012-06-24 16:32:22 +02:00
Maarten Billemont
b7e91358be Use only annotated tags for version determination. 2012-06-24 16:32:22 +02:00
Maarten Billemont
da860d74c4 More internal fixes. 2012-06-24 16:29:51 +02:00
Maarten Billemont
4e2ceb33a0 Fix an odd bug with a missing input handler for certain keyboard types. 2012-06-24 14:53:05 +02:00
Maarten Billemont
d14bde07bd Hardcoded signing profiles to avoid Xcode using the wrong one. 2012-06-23 14:34:26 +02:00
Maarten Billemont
fa2dc822d3 Prettier loading screen and disable Crashlytics debug mode. 2012-06-15 11:34:53 +02:00
Maarten Billemont
d4adafb448 Reset from unlock, FAQ improvements.
[ADDED]     Ability to reset a master password from the unlock screen.
[FIXED]     Manually retain objects that live next to a VC in a
            storyboard within the VC to avoid an OS bug.
[FIXED]     Visibility of the deleteTip.
[ADDED]     An index to the FAQ.
[IMPROVED]  Improved and expanded the FAQ a bit more.
2012-06-15 11:16:02 +02:00
Maarten Billemont
a67d9676ba Improved feedback + logging.
[REMOVED]   Apptentive is now implemented by a standard iOS mail
            composer window and can optionally include logs.
[IMPROVED]  Better inf-level logging of what's going on.
[AUDITED]   Made sure no personal is going out through inf+ levels.
2012-06-14 21:56:54 +02:00
Maarten Billemont
bc2da6a99b Site style updates of what & algorithm pages. 2012-06-12 10:20:06 +02:00
Maarten Billemont
50b5c87f61 Some fixes.
[REMOVED]   Old explanation of saveKey from settings.
[FIXED]     Status-bar display when closing the guide.
[FIXED]     Crash when opening type selection.
2012-06-12 07:52:18 +02:00
Maarten Billemont
d4bcad2658 Small fix of last commit. 2012-06-11 23:44:02 +02:00
Maarten Billemont
13bca309ba Checkpoints update and tag in Localytics too. 2012-06-11 23:39:50 +02:00
Maarten Billemont
83bdb9d626 Checkpoint fixes. 2012-06-11 23:24:23 +02:00
Maarten Billemont
5f1fc453d4 Updated guide + settings bundle + fixes.
[UPDATED]   Guide updated with UI changes.
[FIXED]     Don't animate pushing the unlock VC when appearance of main
            VC is not animated (eg. application startup).
[FIXED]     rememberKey -> rememberLogin in settings bundle.
[REMOVED]   saveKey from settings bundle.
[UPDATED]   Removed lock from the Default image; show a dummy avatar
            instead.
[FIXED]     Don't forget key when signing out.
2012-06-11 23:03:17 +02:00
Maarten Billemont
9c875d4311 Default password type + new user confirmation.
[ADDED]     User preference for default password type.
[RENAMED]   Secure type to Maximum Security.
[FIXED]     Logging bug in password generation.
[ADDED]     Confirmation popup after new user creation.
2012-06-11 16:15:49 +02:00
Maarten Billemont
afafa473a7 Reverted changes to unlock presenting and fixed settings display.
[FIXED]     IASK display by not initializing the VC with the right nib.
2012-06-11 00:53:03 +02:00
Maarten Billemont
4d5f609d72 Fixes.
[REVERTED]  managedObjectContext and persistenceStoreCoordinator needn't
            be lazy anymore.
[FIXED]     AdHoc #def'ed code fixed.
2012-06-10 21:43:18 +02:00
Maarten Billemont
6f1d53ea35 Improved runtime debug logging + new user avatar selection.
[UPDATED]   Crashlytics.
[IMPROVED]  Sending logs and configuration to crashlytics, added
            sendDebugInfo option that allows the user to choose to send
            more info.  Now also sending a device identifier.
[ADDED]     Avatar selection dialog when a new user is created.
2012-06-10 08:36:11 +02:00
Maarten Billemont
a8bf74a925 AppCode code formatting. 2012-06-08 23:46:13 +02:00
Maarten Billemont
d59f77720c Minor improvements. 2012-06-08 21:06:19 +02:00
Maarten Billemont
92be7f7267 Some site layout improvements. 2012-06-08 20:46:08 +02:00
Maarten Billemont
09d5e64c55 Improved signin/signout state logic. 2012-06-08 00:40:30 +02:00
Maarten Billemont
f796888901 Retire version 2 of the site. 2012-06-07 00:27:22 +02:00
Maarten Billemont
679990dc4b Improved algorithm security.
[UPDATED]   Algorithm updated to reflect advice from randombit.net
            cryptography list:
                - Add in a salt (user name) to defeat rainbow tables.
                - Add in a fixed string to scope the algorithm and avoid
                  colliding with someone else's similar or identical
                  algorithm (also helps protect against precalculated
                  rainbow tables).
                - Use HMAC instead of plain SHA to avoid SHA weaknesses.
                  The old implementation wasn't vulnerable to extension
                  attacks or other known weaknesses, but HMAC is a safer
                  choice and will bring up less suspicion.
                - Prefix strings by length as an extra precautionary
                  measure against possible bugs in hash functions.
2012-06-07 00:25:55 +02:00
Maarten Billemont
b472c85c9d Avatar display fixes.
[ADDED]     A new password type: Secure password.  20 characters, not
            word-based, very high entropy.
[FIXED]     UI bugs and improvements with the avatar display and
            password checking state display.
2012-06-06 22:38:43 +02:00
Maarten Billemont
77306e0046 Unlock and user preferences implementation.
[FIXED]     Unlock screen.
[FIXED]     Internal fixes.
[ADDED]     Avatar selection in preferences.
[ADDED]     Implementation of the other preferences.
[IMPROVED]  UI of unlock and preferences screens.
2012-06-06 00:59:09 +02:00
Maarten Billemont
0491ba3f97 Type improvements and fixes.
[IMPROVED]  Cleaner handling of types in entities.
[FIXED]     Lots of little fixes from refactoring and AppCode
            inspections, throughout.
2012-06-05 00:55:02 +02:00
Maarten Billemont
ba299d4674 User support.
[ADDED]     Support for multiple users, each their own master password.
2012-06-04 11:27:02 +02:00
Maarten Billemont
3de9a0c67e Removed apptentive ratings, added some tool tips, and some fixes.
[REMOVED]   Stop using apptentive for rating questions.  Pearl's
            built-in functionality seems nicer and more basic.
[ADDED]     Some more tool-tips to help the user, to be shown on first
            run only.
[FIXED]     The site name tip wasn't showing anymore.
[FIXED]     Some language and formatting in help.html.
2012-05-27 22:35:03 +02:00
Maarten Billemont
7d0ea4b3f5 Some usability improvements + apptentive fixes.
[IMPROVED]  Make persistence more lazy to avoid UI blocks.
[IMPROVED]  Use "Master Password" as CFBundleDisplayName at runtime.  No
            home-screen length restrictions there.
[FIXED]     Inform Apptentive of significant events.
2012-05-27 14:26:13 +02:00
Maarten Billemont
ac14b10752 Integrated Apptentive.
[ADDED]     Experimental support for Apptentive.
2012-05-23 09:30:07 +02:00
Maarten Billemont
2d8f2943e5 Fix headers of other pages of the website. 2012-05-23 08:56:56 +02:00
Maarten Billemont
4c0348b5c8 Disable get satisfaction better. 2012-05-22 00:14:38 +02:00
Maarten Billemont
a5d2f82db4 Site improvements.
[IMPROVED]  Site prepared for production version of Master Password.
[ADDED]     Screenshots and marketing stuff to the site.
[UPDATED]   Texts on the site.
2012-05-21 23:57:06 +02:00
Maarten Billemont
ec8eff2117 Sign distribution builds with distribution profiles.
[FIXED]     When resigning development applications, get-task-allowed
            isn't properly set in the binary.
2012-05-21 08:48:40 +02:00
194 changed files with 5871 additions and 3241 deletions

5
.gitmodules vendored
View File

@@ -6,4 +6,7 @@
url = git://github.com/futuretap/InAppSettingsKit.git
[submodule "External/iCloudStoreManager"]
path = External/iCloudStoreManager
url = git://github.com/alekseyn/iCloudStoreManager.git
url = git://github.com/lhunath/iCloudStoreManager.git
[submodule "External/FontReplacer"]
path = External/FontReplacer
url = git://github.com/0xced/FontReplacer.git

View File

@@ -0,0 +1,9 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
<option name="myLocal" value="false" />
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

View File

@@ -7,6 +7,46 @@
#import <Foundation/Foundation.h>
/**
*
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
*
* Example output:
* -[AppDelegate login:] line 134 $ login start
*
* If you would like to change this macro, create a new header file, unset our define and then define
* your own version. Make sure this new header file is imported after the Crashlytics header file.
*
* #undef CLS_LOG
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
*
**/
#ifdef DEBUG
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#endif
/**
*
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
* and will only be visible in your Crashlytics dashboard.
*
**/
void CLSLog(NSString *format, ...);
/**
*
* Add logging that will be sent with your crash data. This logging will show up in the system.log
* and your Crashlytics dashboard. It is not reccomended for Release builds.
*
**/
void CLSNSLog(NSString *format, ...);
@protocol CrashlyticsDelegate;
@interface Crashlytics : NSObject {
@@ -63,6 +103,49 @@
**/
- (void)crash;
/**
*
* Many of our customers have requested the ability to tie crashes to specific end-users of their
* application in order to facilitate responses to support requests or permit the ability to reach
* out for more information. We allow you to specify up to three separate values for display within
* the Crashlytics UI - but please be mindful of your end-user's privacy.
*
* We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record
* in your system. This could be a database id, hash, or other value that is meaningless to a
* third-party observer but can be indexed and queried by you.
*
* Optionally, you may also specify the end-user's name or username, as well as email address if you
* do not have a system that works well with obscured identifiers.
*
* Pursuant to our EULA, this data is transferred securely throughout our system and we will not
* disseminate end-user data unless required to by law. That said, if you choose to provide end-user
* contact information, we strongly recommend that you disclose this in your application's privacy
* policy. Data privacy is of our utmost concern.
*
**/
- (void)setUserIdentifier:(NSString *)identifier;
- (void)setUserName:(NSString *)name;
- (void)setUserEmail:(NSString *)email;
+ (void)setUserIdentifier:(NSString *)identifier;
+ (void)setUserName:(NSString *)name;
+ (void)setUserEmail:(NSString *)email;
/**
*
* Set a value for a key to be associated with your crash data.
*
**/
- (void)setObjectValue:(id)value forKey:(NSString *)key;
- (void)setIntValue:(int)value forKey:(NSString *)key;
- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
- (void)setFloatValue:(float)value forKey:(NSString *)key;
+ (void)setObjectValue:(id)value forKey:(NSString *)key;
+ (void)setIntValue:(int)value forKey:(NSString *)key;
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
+ (void)setFloatValue:(float)value forKey:(NSString *)key;
@end
/**
@@ -80,7 +163,7 @@
*
* Called once a Crashlytics instance has determined that the last execution of the
* application ended in a crash. This is called some time after the crash reporting
* process has begun. If you have specififed a delay in one of the
* process has begun. If you have specified a delay in one of the
* startWithAPIKey:... calls, this will take at least that long to be invoked.
*
**/

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.1.3</string>
<string>1.1.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
@@ -25,7 +25,7 @@
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>0100.01.03</string>
<string>0101.02.00</string>
<key>CrashlyticsAPIKey</key>
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
<key>DTCompiler</key>

Binary file not shown.

1
External/FontReplacer vendored Submodule

Submodule External/FontReplacer added at 4e3dea0870

2
External/Pearl vendored

View File

@@ -10,14 +10,20 @@
DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; };
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; };
DA0A1D0D15690AD40092735D /* tip_arrow_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0715690AD40092735D /* tip_arrow_banana.png */; };
DA0A1D0E15690AD40092735D /* tip_arrow_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0815690AD40092735D /* tip_arrow_black.png */; };
DA0A1D0F15690AD40092735D /* tip_arrow_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0915690AD40092735D /* tip_arrow_gray.png */; };
DA0A1D1015690AD40092735D /* tip_arrow_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0A15690AD40092735D /* tip_arrow_mercury.png */; };
DA0A1D1115690AD40092735D /* tip_arrow_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0B15690AD40092735D /* tip_arrow_teal.png */; };
DA0A1D1215690AD40092735D /* tip_arrow_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */; };
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; };
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; };
DA0E07961577FE490008A67E /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0E07951577FE490008A67E /* MPEntities.m */; };
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */; };
DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */; };
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9D515723E6900A68B4C /* PearlLazy.h */; };
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2601586099D0079CE6E /* MPUserEntity.m */; };
DA40C2641586099E0079CE6E /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2631586099E0079CE6E /* MPElementEntity.m */; };
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */; };
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */; };
DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA4426001557BF260052177D /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DA4425F11557BF260052177D /* UbiquityStoreManager.h */; };
DA4426011557BF260052177D /* UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4425F21557BF260052177D /* UbiquityStoreManager.m */; };
@@ -35,6 +41,10 @@
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA600C2715056427008E9AB6 /* MPConfig.m */; };
DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; };
DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA829E6015984813002417D3 /* UIFont+Replacement.h in Headers */ = {isa = PBXBuildFile; fileRef = DA829E5E15984812002417D3 /* UIFont+Replacement.h */; };
DA829E6115984813002417D3 /* UIFont+Replacement.m in Sources */ = {isa = PBXBuildFile; fileRef = DA829E5F15984812002417D3 /* UIFont+Replacement.m */; };
DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA829E51159847E0002417D3 /* libFontReplacer.a */; };
DA95D59D14DF063C008D1B94 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA95D5CF14DF0691008D1B94 /* IASKAppSettingsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */; };
DA95D5D014DF0691008D1B94 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */; };
@@ -87,10 +97,7 @@
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; };
DAB8D46B15036BCF00CED3BC /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */; };
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45615036BCF00CED3BC /* MPTypes.m */; };
DAB8D46D15036BCF00CED3BC /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45815036BCF00CED3BC /* MPElementEntity.m */; };
DAB8D46E15036BCF00CED3BC /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */; };
DAB8D6FA15036BF600CED3BC /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47115036BF600CED3BC /* ui_background.png */; };
DAB8D6FB15036BF600CED3BC /* ui_background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47215036BF600CED3BC /* ui_background@2x.png */; };
DAB8D6FC15036BF600CED3BC /* ui_box_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47315036BF600CED3BC /* ui_box_checked.png */; };
@@ -661,72 +668,6 @@
DAB8D93715036BF700CED3BC /* lock_red.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B415036BF600CED3BC /* lock_red.png */; };
DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B515036BF600CED3BC /* lock_red@2x.png */; };
DAB8D93915036BF700CED3BC /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B615036BF600CED3BC /* logo-bare.png */; };
DAB8D93A15036BF700CED3BC /* tip_alert_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B815036BF600CED3BC /* tip_alert_banana.png */; };
DAB8D93B15036BF700CED3BC /* tip_alert_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B915036BF600CED3BC /* tip_alert_banana@2x.png */; };
DAB8D93C15036BF700CED3BC /* tip_alert_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */; };
DAB8D93D15036BF700CED3BC /* tip_alert_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BB15036BF600CED3BC /* tip_alert_black@2x.png */; };
DAB8D93E15036BF700CED3BC /* tip_alert_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BC15036BF600CED3BC /* tip_alert_gray.png */; };
DAB8D93F15036BF700CED3BC /* tip_alert_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BD15036BF600CED3BC /* tip_alert_gray@2x.png */; };
DAB8D94015036BF700CED3BC /* tip_alert_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BE15036BF600CED3BC /* tip_alert_mercury.png */; };
DAB8D94115036BF700CED3BC /* tip_alert_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BF15036BF600CED3BC /* tip_alert_mercury@2x.png */; };
DAB8D94215036BF700CED3BC /* tip_alert_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C015036BF600CED3BC /* tip_alert_teal.png */; };
DAB8D94315036BF700CED3BC /* tip_alert_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C115036BF600CED3BC /* tip_alert_teal@2x.png */; };
DAB8D94415036BF700CED3BC /* tip_alert_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C215036BF600CED3BC /* tip_alert_wood.png */; };
DAB8D94515036BF700CED3BC /* tip_alert_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C315036BF600CED3BC /* tip_alert_wood@2x.png */; };
DAB8D94615036BF700CED3BC /* tip_arrow_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C415036BF600CED3BC /* tip_arrow_banana@2x.png */; };
DAB8D94715036BF700CED3BC /* tip_arrow_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C515036BF600CED3BC /* tip_arrow_black@2x.png */; };
DAB8D94815036BF700CED3BC /* tip_arrow_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C615036BF600CED3BC /* tip_arrow_gray@2x.png */; };
DAB8D94915036BF700CED3BC /* tip_arrow_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C715036BF600CED3BC /* tip_arrow_mercury@2x.png */; };
DAB8D94A15036BF700CED3BC /* tip_arrow_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C815036BF600CED3BC /* tip_arrow_teal@2x.png */; };
DAB8D94B15036BF700CED3BC /* tip_arrow_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C915036BF600CED3BC /* tip_arrow_wood@2x.png */; };
DAB8D94C15036BF700CED3BC /* tip_basic_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CA15036BF600CED3BC /* tip_basic_banana.png */; };
DAB8D94D15036BF700CED3BC /* tip_basic_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CB15036BF600CED3BC /* tip_basic_banana@2x.png */; };
DAB8D94E15036BF700CED3BC /* tip_basic_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CC15036BF600CED3BC /* tip_basic_black.png */; };
DAB8D94F15036BF700CED3BC /* tip_basic_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CD15036BF600CED3BC /* tip_basic_black@2x.png */; };
DAB8D95015036BF700CED3BC /* tip_basic_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CE15036BF600CED3BC /* tip_basic_gray.png */; };
DAB8D95115036BF700CED3BC /* tip_basic_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CF15036BF600CED3BC /* tip_basic_gray@2x.png */; };
DAB8D95215036BF700CED3BC /* tip_basic_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D015036BF600CED3BC /* tip_basic_mercury.png */; };
DAB8D95315036BF700CED3BC /* tip_basic_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D115036BF600CED3BC /* tip_basic_mercury@2x.png */; };
DAB8D95415036BF700CED3BC /* tip_basic_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D215036BF600CED3BC /* tip_basic_teal.png */; };
DAB8D95515036BF700CED3BC /* tip_basic_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D315036BF600CED3BC /* tip_basic_teal@2x.png */; };
DAB8D95615036BF700CED3BC /* tip_basic_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D415036BF600CED3BC /* tip_basic_wood.png */; };
DAB8D95715036BF700CED3BC /* tip_basic_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D515036BF600CED3BC /* tip_basic_wood@2x.png */; };
DAB8D95815036BF700CED3BC /* tip_download_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D615036BF600CED3BC /* tip_download_banana.png */; };
DAB8D95915036BF700CED3BC /* tip_download_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D715036BF600CED3BC /* tip_download_banana@2x.png */; };
DAB8D95A15036BF700CED3BC /* tip_download_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D815036BF600CED3BC /* tip_download_black.png */; };
DAB8D95B15036BF700CED3BC /* tip_download_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D915036BF600CED3BC /* tip_download_black@2x.png */; };
DAB8D95C15036BF700CED3BC /* tip_download_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DA15036BF600CED3BC /* tip_download_gray.png */; };
DAB8D95D15036BF700CED3BC /* tip_download_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DB15036BF600CED3BC /* tip_download_gray@2x.png */; };
DAB8D95E15036BF700CED3BC /* tip_download_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DC15036BF600CED3BC /* tip_download_mercury.png */; };
DAB8D95F15036BF700CED3BC /* tip_download_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DD15036BF600CED3BC /* tip_download_mercury@2x.png */; };
DAB8D96015036BF700CED3BC /* tip_download_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DE15036BF600CED3BC /* tip_download_teal.png */; };
DAB8D96115036BF700CED3BC /* tip_download_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DF15036BF600CED3BC /* tip_download_teal@2x.png */; };
DAB8D96215036BF700CED3BC /* tip_download_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E015036BF600CED3BC /* tip_download_wood.png */; };
DAB8D96315036BF700CED3BC /* tip_download_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E115036BF600CED3BC /* tip_download_wood@2x.png */; };
DAB8D96415036BF700CED3BC /* tip_large_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E215036BF600CED3BC /* tip_large_banana.png */; };
DAB8D96515036BF700CED3BC /* tip_large_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E315036BF600CED3BC /* tip_large_banana@2x.png */; };
DAB8D96615036BF700CED3BC /* tip_large_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E415036BF600CED3BC /* tip_large_black.png */; };
DAB8D96715036BF700CED3BC /* tip_large_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E515036BF600CED3BC /* tip_large_black@2x.png */; };
DAB8D96815036BF700CED3BC /* tip_large_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E615036BF600CED3BC /* tip_large_gray.png */; };
DAB8D96915036BF700CED3BC /* tip_large_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E715036BF600CED3BC /* tip_large_gray@2x.png */; };
DAB8D96A15036BF700CED3BC /* tip_large_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E815036BF600CED3BC /* tip_large_mercury.png */; };
DAB8D96B15036BF700CED3BC /* tip_large_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E915036BF600CED3BC /* tip_large_mercury@2x.png */; };
DAB8D96C15036BF700CED3BC /* tip_large_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EA15036BF600CED3BC /* tip_large_teal.png */; };
DAB8D96D15036BF700CED3BC /* tip_large_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EB15036BF600CED3BC /* tip_large_teal@2x.png */; };
DAB8D96E15036BF700CED3BC /* tip_large_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EC15036BF600CED3BC /* tip_large_wood.png */; };
DAB8D96F15036BF700CED3BC /* tip_large_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6ED15036BF600CED3BC /* tip_large_wood@2x.png */; };
DAB8D97015036BF700CED3BC /* tip_location_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EE15036BF600CED3BC /* tip_location_banana.png */; };
DAB8D97115036BF700CED3BC /* tip_location_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EF15036BF600CED3BC /* tip_location_banana@2x.png */; };
DAB8D97215036BF700CED3BC /* tip_location_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F015036BF600CED3BC /* tip_location_black.png */; };
DAB8D97315036BF700CED3BC /* tip_location_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F115036BF600CED3BC /* tip_location_black@2x.png */; };
DAB8D97415036BF700CED3BC /* tip_location_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F215036BF600CED3BC /* tip_location_gray.png */; };
DAB8D97515036BF700CED3BC /* tip_location_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F315036BF600CED3BC /* tip_location_gray@2x.png */; };
DAB8D97615036BF700CED3BC /* tip_location_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F415036BF600CED3BC /* tip_location_mercury.png */; };
DAB8D97715036BF700CED3BC /* tip_location_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F515036BF600CED3BC /* tip_location_mercury@2x.png */; };
DAB8D97815036BF700CED3BC /* tip_location_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F615036BF600CED3BC /* tip_location_teal.png */; };
DAB8D97915036BF700CED3BC /* tip_location_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F715036BF600CED3BC /* tip_location_teal@2x.png */; };
DAB8D97A15036BF700CED3BC /* tip_location_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F815036BF600CED3BC /* tip_location_wood.png */; };
DAB8D97B15036BF700CED3BC /* tip_location_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F915036BF600CED3BC /* tip_location_wood@2x.png */; };
DAB8D97C1503718B00CED3BC /* jquery-1.6.1.min.js in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6AB15036BF600CED3BC /* jquery-1.6.1.min.js */; };
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
@@ -734,7 +675,16 @@
DAC6327B1486809A0075AEA5 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC632791486809A0075AEA5 /* JRSwizzle.h */; };
DAC6327C1486809A0075AEA5 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC6327A1486809A0075AEA5 /* JRSwizzle.m */; };
DAC632891486D9690075AEA5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; };
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */; };
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DACABB861572A2A7008BA211 /* tip_alert_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */; };
DACABB871572A2A7008BA211 /* tip_alert_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BB15036BF600CED3BC /* tip_alert_black@2x.png */; };
DACABB881572A2A7008BA211 /* tip_basic_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CC15036BF600CED3BC /* tip_basic_black.png */; };
DACABB891572A2A7008BA211 /* tip_basic_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CD15036BF600CED3BC /* tip_basic_black@2x.png */; };
DACABB8C1572A4A5008BA211 /* tip_basic_black_top_right.png in Resources */ = {isa = PBXBuildFile; fileRef = DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */; };
DACABB8D1572A4A5008BA211 /* tip_basic_black_top_right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACABB8B1572A4A4008BA211 /* tip_basic_black_top_right@2x.png */; };
DACABB901572B76A008BA211 /* tip_basic_black_top.png in Resources */ = {isa = PBXBuildFile; fileRef = DACABB8E1572B769008BA211 /* tip_basic_black_top.png */; };
DACABB911572B76A008BA211 /* tip_basic_black_top@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */; };
DAD3126715528C9C00A3F9ED /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3125F15528C9C00A3F9ED /* Crashlytics.framework */; };
DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */ = {isa = PBXBuildFile; fileRef = DAD3126015528C9C00A3F9ED /* Crashlytics.plist */; };
DAD3126915528C9C00A3F9ED /* libTestFlight.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3126215528C9C00A3F9ED /* libTestFlight.a */; };
@@ -756,13 +706,51 @@
DAD312BE1552977200A3F9ED /* UIColor+HSV.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD312BA1552977200A3F9ED /* UIColor+HSV.m */; };
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3127115528CD200A3F9ED /* libLocalytics.a */; };
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD312C01552A20800A3F9ED /* libsqlite3.dylib */; };
DAE4C969157E63BE00EFE047 /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C943157E63BE00EFE047 /* avatar-0.png */; };
DAE4C96A157E63BE00EFE047 /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C944157E63BE00EFE047 /* avatar-0@2x.png */; };
DAE4C96B157E63BE00EFE047 /* avatar-1.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C945157E63BE00EFE047 /* avatar-1.png */; };
DAE4C96C157E63BE00EFE047 /* avatar-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C946157E63BE00EFE047 /* avatar-1@2x.png */; };
DAE4C96D157E63BE00EFE047 /* avatar-2.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C947157E63BE00EFE047 /* avatar-2.png */; };
DAE4C96E157E63BE00EFE047 /* avatar-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C948157E63BE00EFE047 /* avatar-2@2x.png */; };
DAE4C96F157E63BE00EFE047 /* avatar-3.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C949157E63BE00EFE047 /* avatar-3.png */; };
DAE4C970157E63BE00EFE047 /* avatar-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94A157E63BE00EFE047 /* avatar-3@2x.png */; };
DAE4C971157E63BE00EFE047 /* avatar-4.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94B157E63BE00EFE047 /* avatar-4.png */; };
DAE4C972157E63BE00EFE047 /* avatar-4@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94C157E63BE00EFE047 /* avatar-4@2x.png */; };
DAE4C973157E63BE00EFE047 /* avatar-5.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94D157E63BE00EFE047 /* avatar-5.png */; };
DAE4C974157E63BE00EFE047 /* avatar-5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94E157E63BE00EFE047 /* avatar-5@2x.png */; };
DAE4C975157E63BE00EFE047 /* avatar-6.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94F157E63BE00EFE047 /* avatar-6.png */; };
DAE4C976157E63BE00EFE047 /* avatar-6@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C950157E63BE00EFE047 /* avatar-6@2x.png */; };
DAE4C977157E63BE00EFE047 /* avatar-7.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C951157E63BE00EFE047 /* avatar-7.png */; };
DAE4C978157E63BE00EFE047 /* avatar-7@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C952157E63BE00EFE047 /* avatar-7@2x.png */; };
DAE4C979157E63BE00EFE047 /* avatar-8.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C953157E63BE00EFE047 /* avatar-8.png */; };
DAE4C97A157E63BE00EFE047 /* avatar-8@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C954157E63BE00EFE047 /* avatar-8@2x.png */; };
DAE4C97B157E63BE00EFE047 /* avatar-9.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C955157E63BE00EFE047 /* avatar-9.png */; };
DAE4C97C157E63BE00EFE047 /* avatar-9@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C956157E63BE00EFE047 /* avatar-9@2x.png */; };
DAE4C97D157E63BE00EFE047 /* avatar-10.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C957157E63BE00EFE047 /* avatar-10.png */; };
DAE4C97E157E63BE00EFE047 /* avatar-10@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C958157E63BE00EFE047 /* avatar-10@2x.png */; };
DAE4C97F157E63BE00EFE047 /* avatar-11.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C959157E63BE00EFE047 /* avatar-11.png */; };
DAE4C980157E63BE00EFE047 /* avatar-11@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95A157E63BE00EFE047 /* avatar-11@2x.png */; };
DAE4C981157E63BE00EFE047 /* avatar-12.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95B157E63BE00EFE047 /* avatar-12.png */; };
DAE4C982157E63BE00EFE047 /* avatar-12@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95C157E63BE00EFE047 /* avatar-12@2x.png */; };
DAE4C983157E63BE00EFE047 /* avatar-13.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95D157E63BE00EFE047 /* avatar-13.png */; };
DAE4C984157E63BE00EFE047 /* avatar-13@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95E157E63BE00EFE047 /* avatar-13@2x.png */; };
DAE4C985157E63BE00EFE047 /* avatar-14.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95F157E63BE00EFE047 /* avatar-14.png */; };
DAE4C986157E63BE00EFE047 /* avatar-14@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C960157E63BE00EFE047 /* avatar-14@2x.png */; };
DAE4C987157E63BE00EFE047 /* avatar-15.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C961157E63BE00EFE047 /* avatar-15.png */; };
DAE4C988157E63BE00EFE047 /* avatar-15@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C962157E63BE00EFE047 /* avatar-15@2x.png */; };
DAE4C989157E63BE00EFE047 /* avatar-16.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C963157E63BE00EFE047 /* avatar-16.png */; };
DAE4C98A157E63BE00EFE047 /* avatar-16@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C964157E63BE00EFE047 /* avatar-16@2x.png */; };
DAE4C98B157E63BE00EFE047 /* avatar-17.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C965157E63BE00EFE047 /* avatar-17.png */; };
DAE4C98C157E63BE00EFE047 /* avatar-17@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C966157E63BE00EFE047 /* avatar-17@2x.png */; };
DAE4C98D157E63BE00EFE047 /* avatar-18.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C967157E63BE00EFE047 /* avatar-18.png */; };
DAE4C98E157E63BE00EFE047 /* avatar-18@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C968157E63BE00EFE047 /* avatar-18@2x.png */; };
DAEBC45314F6364500987BF6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; };
DAFE4A1315039824003ABA7C /* NSObject_PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */; };
DAFE4A1415039824003ABA7C /* NSObject_PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */; };
DAFE4A1515039824003ABA7C /* NSString_PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString_PearlNSArrayFormat.h */; };
DAFE4A1615039824003ABA7C /* NSString_PearlNSArrayFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DB15039823003ABA7C /* NSString_PearlNSArrayFormat.m */; };
DAFE4A1715039824003ABA7C /* NSString_PearlSEL.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DC15039823003ABA7C /* NSString_PearlSEL.h */; };
DAFE4A1815039824003ABA7C /* NSString_PearlSEL.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DD15039823003ABA7C /* NSString_PearlSEL.m */; };
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */; };
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */; };
DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */; };
DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DB15039823003ABA7C /* NSString+PearlNSArrayFormat.m */; };
DAFE4A1715039824003ABA7C /* NSString+PearlSEL.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DC15039823003ABA7C /* NSString+PearlSEL.h */; };
DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DD15039823003ABA7C /* NSString+PearlSEL.m */; };
DAFE4A1915039824003ABA7C /* Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DE15039823003ABA7C /* Pearl.h */; };
DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */; };
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */; };
@@ -822,11 +810,21 @@
DAFE4A5515039824003ABA7C /* PearlValidatingTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE462115039823003ABA7C /* PearlValidatingTextField.m */; };
DAFE4A5615039824003ABA7C /* PearlWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE462215039823003ABA7C /* PearlWebViewController.h */; };
DAFE4A5715039824003ABA7C /* PearlWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE462315039823003ABA7C /* PearlWebViewController.m */; };
DAFE4A5815039824003ABA7C /* UIImage_PearlScaling.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A1115039824003ABA7C /* UIImage_PearlScaling.h */; };
DAFE4A5915039824003ABA7C /* UIImage_PearlScaling.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */; };
DAFE4A5815039824003ABA7C /* UIImage+PearlScaling.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A1115039824003ABA7C /* UIImage+PearlScaling.h */; };
DAFE4A5915039824003ABA7C /* UIImage+PearlScaling.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A1215039824003ABA7C /* UIImage+PearlScaling.m */; };
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAFE45FA15039823003ABA7C /* Pearl.strings */; };
DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */; };
DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */; };
DAFE4A63150399FF003ABA82 /* UIControl+PearlBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */; };
DAFE4A63150399FF003ABA84 /* UIControl+PearlBlocks.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */; };
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */; };
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */; };
DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */; };
DAFE4A63150399FF003ABA8C /* UIControl+PearlSelect.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */; };
DAFE4A63150399FF003ABA8E /* UIScrollView+PearlFlashingIndicators.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA8D /* UIScrollView+PearlFlashingIndicators.m */; };
DAFE4A63150399FF003ABA90 /* UIScrollView+PearlFlashingIndicators.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA8F /* UIScrollView+PearlFlashingIndicators.h */; };
DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */; };
DAFE4A63150399FF003ABA94 /* NSDateFormatter+RFC3339.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -886,6 +884,23 @@
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tip_arrow_wood.png; path = Resources/Tooltips/tip_arrow_wood.png; sourceTree = SOURCE_ROOT; };
DA0A1D1315690AF30092735D /* Icon-72@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72@2x.png"; sourceTree = "<group>"; };
DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Crypto.m"; sourceTree = "<group>"; };
DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-UIKit.m"; sourceTree = "<group>"; };
DA30E9D515723E6900A68B4C /* PearlLazy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLazy.h; sourceTree = "<group>"; };
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
DA40C25F1586099D0079CE6E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DA40C2601586099D0079CE6E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DA40C2621586099E0079CE6E /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DA40C2631586099E0079CE6E /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libiCloudStoreManager.a; sourceTree = BUILT_PRODUCTS_DIR; };
DA4425F11557BF260052177D /* UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UbiquityStoreManager.h; sourceTree = "<group>"; };
DA4425F21557BF260052177D /* UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UbiquityStoreManager.m; sourceTree = "<group>"; };
@@ -905,6 +920,10 @@
DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios.a"; sourceTree = "<group>"; };
DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = "<group>"; };
DA829E51159847E0002417D3 /* libFontReplacer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFontReplacer.a; sourceTree = BUILT_PRODUCTS_DIR; };
DA829E5E15984812002417D3 /* UIFont+Replacement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+Replacement.h"; sourceTree = "<group>"; };
DA829E5F15984812002417D3 /* UIFont+Replacement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+Replacement.m"; sourceTree = "<group>"; };
DA902BD01576CA4A00C38161 /* keypad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = keypad.png; sourceTree = "<group>"; };
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libInAppSettingsKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsViewController.h; sourceTree = "<group>"; };
DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsViewController.m; sourceTree = "<group>"; };
@@ -943,6 +962,7 @@
DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKPSToggleSwitchSpecifierViewCell.xib; sourceTree = "<group>"; };
DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = "<group>"; };
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MasterPassword.xcdatamodel; sourceTree = "<group>"; };
DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
DAB8D44115036BCF00CED3BC /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@@ -965,14 +985,8 @@
DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = "<group>"; };
DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = "<group>"; };
DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; };
DAB8D45715036BCF00CED3BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
DAB8D47315036BF600CED3BC /* ui_box_checked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_box_checked.png; sourceTree = "<group>"; };
@@ -1619,8 +1633,14 @@
DAC632791486809A0075AEA5 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = External/Pearl/External/jrswizzle/JRSwizzle.h; sourceTree = SOURCE_ROOT; };
DAC6327A1486809A0075AEA5 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JRSwizzle.m; path = External/Pearl/External/jrswizzle/JRSwizzle.m; sourceTree = SOURCE_ROOT; };
DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
DAC728C8157C247B00889EF2 /* MPPreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPreferencesViewController.h; sourceTree = "<group>"; };
DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPreferencesViewController.m; sourceTree = "<group>"; };
DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Pearl-Prefix.pch"; sourceTree = "<group>"; };
DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_top_right.png; sourceTree = "<group>"; };
DACABB8B1572A4A4008BA211 /* tip_basic_black_top_right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_basic_black_top_right@2x.png"; sourceTree = "<group>"; };
DACABB8E1572B769008BA211 /* tip_basic_black_top.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_top.png; sourceTree = "<group>"; };
DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_basic_black_top@2x.png"; sourceTree = "<group>"; };
DAD3125F15528C9C00A3F9ED /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
DAD3126015528C9C00A3F9ED /* Crashlytics.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Crashlytics.plist; sourceTree = "<group>"; };
DAD3126215528C9C00A3F9ED /* libTestFlight.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libTestFlight.a; sourceTree = "<group>"; };
@@ -1642,13 +1662,51 @@
DAD312B91552977200A3F9ED /* UIColor+HSV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+HSV.h"; path = "External/Pearl/External/uicolor-utilities/UIColor+HSV.h"; sourceTree = SOURCE_ROOT; };
DAD312BA1552977200A3F9ED /* UIColor+HSV.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor+HSV.m"; path = "External/Pearl/External/uicolor-utilities/UIColor+HSV.m"; sourceTree = SOURCE_ROOT; };
DAD312C01552A20800A3F9ED /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
DAE4C943157E63BE00EFE047 /* avatar-0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-0.png"; sourceTree = "<group>"; };
DAE4C944157E63BE00EFE047 /* avatar-0@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-0@2x.png"; sourceTree = "<group>"; };
DAE4C945157E63BE00EFE047 /* avatar-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-1.png"; sourceTree = "<group>"; };
DAE4C946157E63BE00EFE047 /* avatar-1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-1@2x.png"; sourceTree = "<group>"; };
DAE4C947157E63BE00EFE047 /* avatar-2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-2.png"; sourceTree = "<group>"; };
DAE4C948157E63BE00EFE047 /* avatar-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-2@2x.png"; sourceTree = "<group>"; };
DAE4C949157E63BE00EFE047 /* avatar-3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-3.png"; sourceTree = "<group>"; };
DAE4C94A157E63BE00EFE047 /* avatar-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-3@2x.png"; sourceTree = "<group>"; };
DAE4C94B157E63BE00EFE047 /* avatar-4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-4.png"; sourceTree = "<group>"; };
DAE4C94C157E63BE00EFE047 /* avatar-4@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-4@2x.png"; sourceTree = "<group>"; };
DAE4C94D157E63BE00EFE047 /* avatar-5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-5.png"; sourceTree = "<group>"; };
DAE4C94E157E63BE00EFE047 /* avatar-5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-5@2x.png"; sourceTree = "<group>"; };
DAE4C94F157E63BE00EFE047 /* avatar-6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-6.png"; sourceTree = "<group>"; };
DAE4C950157E63BE00EFE047 /* avatar-6@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-6@2x.png"; sourceTree = "<group>"; };
DAE4C951157E63BE00EFE047 /* avatar-7.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-7.png"; sourceTree = "<group>"; };
DAE4C952157E63BE00EFE047 /* avatar-7@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-7@2x.png"; sourceTree = "<group>"; };
DAE4C953157E63BE00EFE047 /* avatar-8.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-8.png"; sourceTree = "<group>"; };
DAE4C954157E63BE00EFE047 /* avatar-8@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-8@2x.png"; sourceTree = "<group>"; };
DAE4C955157E63BE00EFE047 /* avatar-9.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-9.png"; sourceTree = "<group>"; };
DAE4C956157E63BE00EFE047 /* avatar-9@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-9@2x.png"; sourceTree = "<group>"; };
DAE4C957157E63BE00EFE047 /* avatar-10.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-10.png"; sourceTree = "<group>"; };
DAE4C958157E63BE00EFE047 /* avatar-10@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-10@2x.png"; sourceTree = "<group>"; };
DAE4C959157E63BE00EFE047 /* avatar-11.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-11.png"; sourceTree = "<group>"; };
DAE4C95A157E63BE00EFE047 /* avatar-11@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-11@2x.png"; sourceTree = "<group>"; };
DAE4C95B157E63BE00EFE047 /* avatar-12.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-12.png"; sourceTree = "<group>"; };
DAE4C95C157E63BE00EFE047 /* avatar-12@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-12@2x.png"; sourceTree = "<group>"; };
DAE4C95D157E63BE00EFE047 /* avatar-13.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-13.png"; sourceTree = "<group>"; };
DAE4C95E157E63BE00EFE047 /* avatar-13@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-13@2x.png"; sourceTree = "<group>"; };
DAE4C95F157E63BE00EFE047 /* avatar-14.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-14.png"; sourceTree = "<group>"; };
DAE4C960157E63BE00EFE047 /* avatar-14@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-14@2x.png"; sourceTree = "<group>"; };
DAE4C961157E63BE00EFE047 /* avatar-15.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-15.png"; sourceTree = "<group>"; };
DAE4C962157E63BE00EFE047 /* avatar-15@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-15@2x.png"; sourceTree = "<group>"; };
DAE4C963157E63BE00EFE047 /* avatar-16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-16.png"; sourceTree = "<group>"; };
DAE4C964157E63BE00EFE047 /* avatar-16@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-16@2x.png"; sourceTree = "<group>"; };
DAE4C965157E63BE00EFE047 /* avatar-17.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-17.png"; sourceTree = "<group>"; };
DAE4C966157E63BE00EFE047 /* avatar-17@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-17@2x.png"; sourceTree = "<group>"; };
DAE4C967157E63BE00EFE047 /* avatar-18.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-18.png"; sourceTree = "<group>"; };
DAE4C968157E63BE00EFE047 /* avatar-18@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-18@2x.png"; sourceTree = "<group>"; };
DAEBC45214F6364500987BF6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSObject_PearlExport.h; sourceTree = "<group>"; };
DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObject_PearlExport.m; sourceTree = "<group>"; };
DAFE45DA15039823003ABA7C /* NSString_PearlNSArrayFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSString_PearlNSArrayFormat.h; sourceTree = "<group>"; };
DAFE45DB15039823003ABA7C /* NSString_PearlNSArrayFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSString_PearlNSArrayFormat.m; sourceTree = "<group>"; };
DAFE45DC15039823003ABA7C /* NSString_PearlSEL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSString_PearlSEL.h; sourceTree = "<group>"; };
DAFE45DD15039823003ABA7C /* NSString_PearlSEL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSString_PearlSEL.m; sourceTree = "<group>"; };
DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlExport.h"; sourceTree = "<group>"; };
DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlExport.m"; sourceTree = "<group>"; };
DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PearlNSArrayFormat.h"; sourceTree = "<group>"; };
DAFE45DB15039823003ABA7C /* NSString+PearlNSArrayFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+PearlNSArrayFormat.m"; sourceTree = "<group>"; };
DAFE45DC15039823003ABA7C /* NSString+PearlSEL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PearlSEL.h"; sourceTree = "<group>"; };
DAFE45DD15039823003ABA7C /* NSString+PearlSEL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+PearlSEL.m"; sourceTree = "<group>"; };
DAFE45DE15039823003ABA7C /* Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pearl.h; sourceTree = "<group>"; };
DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlAbstractStrings.h; sourceTree = "<group>"; };
DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlAbstractStrings.m; sourceTree = "<group>"; };
@@ -1712,10 +1770,20 @@
DAFE462215039823003ABA7C /* PearlWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlWebViewController.h; sourceTree = "<group>"; };
DAFE462315039823003ABA7C /* PearlWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlWebViewController.m; sourceTree = "<group>"; };
DAFE462415039823003ABA7C /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = "<group>"; };
DAFE4A1115039824003ABA7C /* UIImage_PearlScaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIImage_PearlScaling.h; sourceTree = "<group>"; };
DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImage_PearlScaling.m; sourceTree = "<group>"; };
DAFE4A1115039824003ABA7C /* UIImage+PearlScaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+PearlScaling.h"; sourceTree = "<group>"; };
DAFE4A1215039824003ABA7C /* UIImage+PearlScaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+PearlScaling.m"; sourceTree = "<group>"; };
DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlAppDelegate.m; sourceTree = "<group>"; };
DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlAppDelegate.h; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+PearlBlocks.m"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+PearlBlocks.h"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlKVO.m"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlKVO.h"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+PearlSelect.m"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+PearlSelect.h"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA8D /* UIScrollView+PearlFlashingIndicators.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+PearlFlashingIndicators.m"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA8F /* UIScrollView+PearlFlashingIndicators.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+PearlFlashingIndicators.h"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDateFormatter+RFC3339.m"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDateFormatter+RFC3339.h"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -1731,6 +1799,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */,
DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */,
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */,
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */,
@@ -1751,6 +1820,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DA829E4E159847E0002417D3 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA95D59914DF063C008D1B94 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -1822,6 +1899,7 @@
DAD3126115528C9C00A3F9ED /* TestFlight */,
DAD3127315528CD200A3F9ED /* Localytics */,
DA4425D71557BF260052177D /* iCloudStoreManager */,
DA829E5D15984812002417D3 /* FontReplacer */,
DA5BFA47147E415C00F98B1E /* Frameworks */,
DA5BFA45147E415C00F98B1E /* Products */,
);
@@ -1837,6 +1915,7 @@
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */,
DAD3127115528CD200A3F9ED /* libLocalytics.a */,
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */,
DA829E51159847E0002417D3 /* libFontReplacer.a */,
);
name = Products;
sourceTree = "<group>";
@@ -1844,6 +1923,7 @@
DA5BFA47147E415C00F98B1E /* Frameworks */ = {
isa = PBXGroup;
children = (
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */,
DAD312C01552A20800A3F9ED /* libsqlite3.dylib */,
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */,
DABB981515100B4000B05417 /* SystemConfiguration.framework */,
@@ -1862,8 +1942,17 @@
DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
isa = PBXGroup;
children = (
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */,
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */,
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */,
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */,
DA40C2621586099E0079CE6E /* MPElementEntity.h */,
DA40C2631586099E0079CE6E /* MPElementEntity.m */,
DA40C25F1586099D0079CE6E /* MPUserEntity.h */,
DA40C2601586099D0079CE6E /* MPUserEntity.m */,
DA0E07941577FE490008A67E /* MPEntities.h */,
DA0E07951577FE490008A67E /* MPEntities.m */,
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
DAB8D43E15036BCF00CED3BC /* iOS */,
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
@@ -1872,14 +1961,9 @@
DA4426071557C1990052177D /* MPAppDelegate_Store.m */,
DA600C2615056427008E9AB6 /* MPConfig.h */,
DA600C2715056427008E9AB6 /* MPConfig.m */,
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */,
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */,
DAB8D45915036BCF00CED3BC /* MPTypes.h */,
DAB8D45615036BCF00CED3BC /* MPTypes.m */,
DAB8D45715036BCF00CED3BC /* MPElementEntity.h */,
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */,
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */,
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */,
DAB8D43E15036BCF00CED3BC /* iOS */,
);
path = MasterPassword;
sourceTree = "<group>";
@@ -1894,6 +1978,61 @@
name = Products;
sourceTree = "<group>";
};
DA829E5D15984812002417D3 /* FontReplacer */ = {
isa = PBXGroup;
children = (
DA829E5E15984812002417D3 /* UIFont+Replacement.h */,
DA829E5F15984812002417D3 /* UIFont+Replacement.m */,
);
name = FontReplacer;
path = "External/FontReplacer/UIFont+Replacement";
sourceTree = "<group>";
};
DA902B931576C0FB00C38161 /* Avatars */ = {
isa = PBXGroup;
children = (
DAE4C943157E63BE00EFE047 /* avatar-0.png */,
DAE4C944157E63BE00EFE047 /* avatar-0@2x.png */,
DAE4C945157E63BE00EFE047 /* avatar-1.png */,
DAE4C946157E63BE00EFE047 /* avatar-1@2x.png */,
DAE4C947157E63BE00EFE047 /* avatar-2.png */,
DAE4C948157E63BE00EFE047 /* avatar-2@2x.png */,
DAE4C949157E63BE00EFE047 /* avatar-3.png */,
DAE4C94A157E63BE00EFE047 /* avatar-3@2x.png */,
DAE4C94B157E63BE00EFE047 /* avatar-4.png */,
DAE4C94C157E63BE00EFE047 /* avatar-4@2x.png */,
DAE4C94D157E63BE00EFE047 /* avatar-5.png */,
DAE4C94E157E63BE00EFE047 /* avatar-5@2x.png */,
DAE4C94F157E63BE00EFE047 /* avatar-6.png */,
DAE4C950157E63BE00EFE047 /* avatar-6@2x.png */,
DAE4C951157E63BE00EFE047 /* avatar-7.png */,
DAE4C952157E63BE00EFE047 /* avatar-7@2x.png */,
DAE4C953157E63BE00EFE047 /* avatar-8.png */,
DAE4C954157E63BE00EFE047 /* avatar-8@2x.png */,
DAE4C955157E63BE00EFE047 /* avatar-9.png */,
DAE4C956157E63BE00EFE047 /* avatar-9@2x.png */,
DAE4C957157E63BE00EFE047 /* avatar-10.png */,
DAE4C958157E63BE00EFE047 /* avatar-10@2x.png */,
DAE4C959157E63BE00EFE047 /* avatar-11.png */,
DAE4C95A157E63BE00EFE047 /* avatar-11@2x.png */,
DAE4C95B157E63BE00EFE047 /* avatar-12.png */,
DAE4C95C157E63BE00EFE047 /* avatar-12@2x.png */,
DAE4C95D157E63BE00EFE047 /* avatar-13.png */,
DAE4C95E157E63BE00EFE047 /* avatar-13@2x.png */,
DAE4C95F157E63BE00EFE047 /* avatar-14.png */,
DAE4C960157E63BE00EFE047 /* avatar-14@2x.png */,
DAE4C961157E63BE00EFE047 /* avatar-15.png */,
DAE4C962157E63BE00EFE047 /* avatar-15@2x.png */,
DAE4C963157E63BE00EFE047 /* avatar-16.png */,
DAE4C964157E63BE00EFE047 /* avatar-16@2x.png */,
DAE4C965157E63BE00EFE047 /* avatar-17.png */,
DAE4C966157E63BE00EFE047 /* avatar-17@2x.png */,
DAE4C967157E63BE00EFE047 /* avatar-18.png */,
DAE4C968157E63BE00EFE047 /* avatar-18@2x.png */,
);
path = Avatars;
sourceTree = "<group>";
};
DA95D59E14DF063C008D1B94 /* InAppSettingsKit */ = {
isa = PBXGroup;
children = (
@@ -1986,6 +2125,8 @@
DAB8D44B15036BCF00CED3BC /* MPGuideViewController.m */,
DAB8D44C15036BCF00CED3BC /* MPMainViewController.h */,
DAB8D44D15036BCF00CED3BC /* MPMainViewController.m */,
DAC728C8157C247B00889EF2 /* MPPreferencesViewController.h */,
DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */,
DAB8D44E15036BCF00CED3BC /* MPSearchDelegate.h */,
DAB8D44F15036BCF00CED3BC /* MPSearchDelegate.m */,
DAB8D45015036BCF00CED3BC /* MPTypeViewController.h */,
@@ -2000,6 +2141,8 @@
DAB8D46F15036BF600CED3BC /* Resources */ = {
isa = PBXGroup;
children = (
DA902BD01576CA4A00C38161 /* keypad.png */,
DA902B931576C0FB00C38161 /* Avatars */,
DAB8D47015036BF600CED3BC /* Automaton */,
DAB8D4CE15036BF600CED3BC /* Background */,
DAB8D4D215036BF600CED3BC /* ciphers.plist */,
@@ -2637,12 +2780,10 @@
DAB8D6B715036BF600CED3BC /* Tooltips */ = {
isa = PBXGroup;
children = (
DA0A1D0715690AD40092735D /* tip_arrow_banana.png */,
DA0A1D0815690AD40092735D /* tip_arrow_black.png */,
DA0A1D0915690AD40092735D /* tip_arrow_gray.png */,
DA0A1D0A15690AD40092735D /* tip_arrow_mercury.png */,
DA0A1D0B15690AD40092735D /* tip_arrow_teal.png */,
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */,
DACABB8E1572B769008BA211 /* tip_basic_black_top.png */,
DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */,
DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */,
DACABB8B1572A4A4008BA211 /* tip_basic_black_top_right@2x.png */,
DAB8D6B815036BF600CED3BC /* tip_alert_banana.png */,
DAB8D6B915036BF600CED3BC /* tip_alert_banana@2x.png */,
DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */,
@@ -2655,11 +2796,17 @@
DAB8D6C115036BF600CED3BC /* tip_alert_teal@2x.png */,
DAB8D6C215036BF600CED3BC /* tip_alert_wood.png */,
DAB8D6C315036BF600CED3BC /* tip_alert_wood@2x.png */,
DA0A1D0715690AD40092735D /* tip_arrow_banana.png */,
DAB8D6C415036BF600CED3BC /* tip_arrow_banana@2x.png */,
DA0A1D0815690AD40092735D /* tip_arrow_black.png */,
DAB8D6C515036BF600CED3BC /* tip_arrow_black@2x.png */,
DA0A1D0915690AD40092735D /* tip_arrow_gray.png */,
DAB8D6C615036BF600CED3BC /* tip_arrow_gray@2x.png */,
DA0A1D0A15690AD40092735D /* tip_arrow_mercury.png */,
DAB8D6C715036BF600CED3BC /* tip_arrow_mercury@2x.png */,
DA0A1D0B15690AD40092735D /* tip_arrow_teal.png */,
DAB8D6C815036BF600CED3BC /* tip_arrow_teal@2x.png */,
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */,
DAB8D6C915036BF600CED3BC /* tip_arrow_wood@2x.png */,
DAB8D6CA15036BF600CED3BC /* tip_basic_banana.png */,
DAB8D6CB15036BF600CED3BC /* tip_basic_banana@2x.png */,
@@ -2823,13 +2970,20 @@
DAFE45D715039823003ABA7C /* Pearl */ = {
isa = PBXGroup;
children = (
DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */,
DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */,
DAFE45DA15039823003ABA7C /* NSString_PearlNSArrayFormat.h */,
DAFE45DB15039823003ABA7C /* NSString_PearlNSArrayFormat.m */,
DAFE45DC15039823003ABA7C /* NSString_PearlSEL.h */,
DAFE45DD15039823003ABA7C /* NSString_PearlSEL.m */,
DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */,
DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */,
DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */,
DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */,
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */,
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */,
DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */,
DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */,
DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */,
DAFE45DB15039823003ABA7C /* NSString+PearlNSArrayFormat.m */,
DAFE45DC15039823003ABA7C /* NSString+PearlSEL.h */,
DAFE45DD15039823003ABA7C /* NSString+PearlSEL.m */,
DAFE45DE15039823003ABA7C /* Pearl.h */,
DA30E9CD15722ECA00A68B4C /* Pearl.m */,
DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */,
DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */,
DAFE45E315039823003ABA7C /* PearlCodeUtils.h */,
@@ -2840,6 +2994,8 @@
DAFE45E815039823003ABA7C /* PearlDeviceUtils.m */,
DAFE45E915039823003ABA7C /* PearlInfoPlist.h */,
DAFE45EA15039823003ABA7C /* PearlInfoPlist.m */,
DA30E9D515723E6900A68B4C /* PearlLazy.h */,
DA30E9D615723E6900A68B4C /* PearlLazy.m */,
DAFE45EB15039823003ABA7C /* PearlLogger.h */,
DAFE45EC15039823003ABA7C /* PearlLogger.m */,
DAFE45ED15039823003ABA7C /* PearlMathUtils.h */,
@@ -2871,6 +3027,7 @@
children = (
DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */,
DAFE45FD15039823003ABA7C /* Pearl-Crypto.h */,
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */,
DAFE45FE15039823003ABA7C /* PearlCryptUtils.h */,
DAFE45FF15039823003ABA7C /* PearlCryptUtils.m */,
DAFE460015039823003ABA7C /* PearlKeyChain.h */,
@@ -2888,8 +3045,15 @@
DAFE460715039823003ABA7C /* Pearl-UIKit */ = {
isa = PBXGroup;
children = (
DAFE4A63150399FF003ABA8F /* UIScrollView+PearlFlashingIndicators.h */,
DAFE4A63150399FF003ABA8D /* UIScrollView+PearlFlashingIndicators.m */,
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */,
DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */,
DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */,
DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */,
DAFE460815039823003ABA7C /* Pearl-UIKit-Dependencies.h */,
DAFE460915039823003ABA7C /* Pearl-UIKit.h */,
DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */,
DAFE460A15039823003ABA7C /* PearlAlert.h */,
DAFE460B15039823003ABA7C /* PearlAlert.m */,
DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */,
@@ -2920,8 +3084,8 @@
DAFE462315039823003ABA7C /* PearlWebViewController.m */,
DAFE462415039823003ABA7C /* README */,
DAFE462515039823003ABA7C /* Resources */,
DAFE4A1115039824003ABA7C /* UIImage_PearlScaling.h */,
DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */,
DAFE4A1115039824003ABA7C /* UIImage+PearlScaling.h */,
DAFE4A1215039824003ABA7C /* UIImage+PearlScaling.m */,
);
name = "Pearl-UIKit";
path = "External/Pearl/Pearl-UIKit";
@@ -2945,6 +3109,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DA829E4F159847E0002417D3 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DA829E6015984813002417D3 /* UIFont+Replacement.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA95D59A14DF063C008D1B94 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
@@ -2988,9 +3160,9 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DAFE4A1315039824003ABA7C /* NSObject_PearlExport.h in Headers */,
DAFE4A1515039824003ABA7C /* NSString_PearlNSArrayFormat.h in Headers */,
DAFE4A1715039824003ABA7C /* NSString_PearlSEL.h in Headers */,
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */,
DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */,
DAFE4A1715039824003ABA7C /* NSString+PearlSEL.h in Headers */,
DAFE4A1915039824003ABA7C /* Pearl.h in Headers */,
DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */,
DAFE4A1E15039824003ABA7C /* PearlCodeUtils.h in Headers */,
@@ -3023,8 +3195,15 @@
DAFE4A5215039824003ABA7C /* PearlUIUtils.h in Headers */,
DAFE4A5415039824003ABA7C /* PearlValidatingTextField.h in Headers */,
DAFE4A5615039824003ABA7C /* PearlWebViewController.h in Headers */,
DAFE4A5815039824003ABA7C /* UIImage_PearlScaling.h in Headers */,
DAFE4A5815039824003ABA7C /* UIImage+PearlScaling.h in Headers */,
DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */,
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */,
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */,
DAFE4A63150399FF003ABA84 /* UIControl+PearlBlocks.h in Headers */,
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */,
DAFE4A63150399FF003ABA8C /* UIControl+PearlSelect.h in Headers */,
DAFE4A63150399FF003ABA90 /* UIScrollView+PearlFlashingIndicators.h in Headers */,
DAFE4A63150399FF003ABA94 /* NSDateFormatter+RFC3339.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3079,6 +3258,23 @@
productReference = DA5BFA44147E415C00F98B1E /* MasterPassword.app */;
productType = "com.apple.product-type.application";
};
DA829E50159847E0002417D3 /* FontReplacer */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA829E59159847E0002417D3 /* Build configuration list for PBXNativeTarget "FontReplacer" */;
buildPhases = (
DA829E4D159847E0002417D3 /* Sources */,
DA829E4E159847E0002417D3 /* Frameworks */,
DA829E4F159847E0002417D3 /* Headers */,
);
buildRules = (
);
dependencies = (
);
name = FontReplacer;
productName = FontReplacer;
productReference = DA829E51159847E0002417D3 /* libFontReplacer.a */;
productType = "com.apple.product-type.library.static";
};
DA95D59B14DF063C008D1B94 /* InAppSettingsKit */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */;
@@ -3203,6 +3399,7 @@
DA95D59B14DF063C008D1B94 /* InAppSettingsKit */,
DAD3127015528CD200A3F9ED /* Localytics */,
DA4425CA1557BED40052177D /* iCloudStoreManager */,
DA829E50159847E0002417D3 /* FontReplacer */,
);
};
/* End PBXProject section */
@@ -3817,86 +4014,60 @@
DAB8D93715036BF700CED3BC /* lock_red.png in Resources */,
DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */,
DAB8D93915036BF700CED3BC /* logo-bare.png in Resources */,
DAB8D93A15036BF700CED3BC /* tip_alert_banana.png in Resources */,
DAB8D93B15036BF700CED3BC /* tip_alert_banana@2x.png in Resources */,
DAB8D93C15036BF700CED3BC /* tip_alert_black.png in Resources */,
DAB8D93D15036BF700CED3BC /* tip_alert_black@2x.png in Resources */,
DAB8D93E15036BF700CED3BC /* tip_alert_gray.png in Resources */,
DAB8D93F15036BF700CED3BC /* tip_alert_gray@2x.png in Resources */,
DAB8D94015036BF700CED3BC /* tip_alert_mercury.png in Resources */,
DAB8D94115036BF700CED3BC /* tip_alert_mercury@2x.png in Resources */,
DAB8D94215036BF700CED3BC /* tip_alert_teal.png in Resources */,
DAB8D94315036BF700CED3BC /* tip_alert_teal@2x.png in Resources */,
DAB8D94415036BF700CED3BC /* tip_alert_wood.png in Resources */,
DAB8D94515036BF700CED3BC /* tip_alert_wood@2x.png in Resources */,
DAB8D94615036BF700CED3BC /* tip_arrow_banana@2x.png in Resources */,
DAB8D94715036BF700CED3BC /* tip_arrow_black@2x.png in Resources */,
DAB8D94815036BF700CED3BC /* tip_arrow_gray@2x.png in Resources */,
DAB8D94915036BF700CED3BC /* tip_arrow_mercury@2x.png in Resources */,
DAB8D94A15036BF700CED3BC /* tip_arrow_teal@2x.png in Resources */,
DAB8D94B15036BF700CED3BC /* tip_arrow_wood@2x.png in Resources */,
DAB8D94C15036BF700CED3BC /* tip_basic_banana.png in Resources */,
DAB8D94D15036BF700CED3BC /* tip_basic_banana@2x.png in Resources */,
DAB8D94E15036BF700CED3BC /* tip_basic_black.png in Resources */,
DAB8D94F15036BF700CED3BC /* tip_basic_black@2x.png in Resources */,
DAB8D95015036BF700CED3BC /* tip_basic_gray.png in Resources */,
DAB8D95115036BF700CED3BC /* tip_basic_gray@2x.png in Resources */,
DAB8D95215036BF700CED3BC /* tip_basic_mercury.png in Resources */,
DAB8D95315036BF700CED3BC /* tip_basic_mercury@2x.png in Resources */,
DAB8D95415036BF700CED3BC /* tip_basic_teal.png in Resources */,
DAB8D95515036BF700CED3BC /* tip_basic_teal@2x.png in Resources */,
DAB8D95615036BF700CED3BC /* tip_basic_wood.png in Resources */,
DAB8D95715036BF700CED3BC /* tip_basic_wood@2x.png in Resources */,
DAB8D95815036BF700CED3BC /* tip_download_banana.png in Resources */,
DAB8D95915036BF700CED3BC /* tip_download_banana@2x.png in Resources */,
DAB8D95A15036BF700CED3BC /* tip_download_black.png in Resources */,
DAB8D95B15036BF700CED3BC /* tip_download_black@2x.png in Resources */,
DAB8D95C15036BF700CED3BC /* tip_download_gray.png in Resources */,
DAB8D95D15036BF700CED3BC /* tip_download_gray@2x.png in Resources */,
DAB8D95E15036BF700CED3BC /* tip_download_mercury.png in Resources */,
DAB8D95F15036BF700CED3BC /* tip_download_mercury@2x.png in Resources */,
DAB8D96015036BF700CED3BC /* tip_download_teal.png in Resources */,
DAB8D96115036BF700CED3BC /* tip_download_teal@2x.png in Resources */,
DAB8D96215036BF700CED3BC /* tip_download_wood.png in Resources */,
DAB8D96315036BF700CED3BC /* tip_download_wood@2x.png in Resources */,
DAB8D96415036BF700CED3BC /* tip_large_banana.png in Resources */,
DAB8D96515036BF700CED3BC /* tip_large_banana@2x.png in Resources */,
DAB8D96615036BF700CED3BC /* tip_large_black.png in Resources */,
DAB8D96715036BF700CED3BC /* tip_large_black@2x.png in Resources */,
DAB8D96815036BF700CED3BC /* tip_large_gray.png in Resources */,
DAB8D96915036BF700CED3BC /* tip_large_gray@2x.png in Resources */,
DAB8D96A15036BF700CED3BC /* tip_large_mercury.png in Resources */,
DAB8D96B15036BF700CED3BC /* tip_large_mercury@2x.png in Resources */,
DAB8D96C15036BF700CED3BC /* tip_large_teal.png in Resources */,
DAB8D96D15036BF700CED3BC /* tip_large_teal@2x.png in Resources */,
DAB8D96E15036BF700CED3BC /* tip_large_wood.png in Resources */,
DAB8D96F15036BF700CED3BC /* tip_large_wood@2x.png in Resources */,
DAB8D97015036BF700CED3BC /* tip_location_banana.png in Resources */,
DAB8D97115036BF700CED3BC /* tip_location_banana@2x.png in Resources */,
DAB8D97215036BF700CED3BC /* tip_location_black.png in Resources */,
DAB8D97315036BF700CED3BC /* tip_location_black@2x.png in Resources */,
DAB8D97415036BF700CED3BC /* tip_location_gray.png in Resources */,
DAB8D97515036BF700CED3BC /* tip_location_gray@2x.png in Resources */,
DAB8D97615036BF700CED3BC /* tip_location_mercury.png in Resources */,
DAB8D97715036BF700CED3BC /* tip_location_mercury@2x.png in Resources */,
DAB8D97815036BF700CED3BC /* tip_location_teal.png in Resources */,
DAB8D97915036BF700CED3BC /* tip_location_teal@2x.png in Resources */,
DAB8D97A15036BF700CED3BC /* tip_location_wood.png in Resources */,
DAB8D97B15036BF700CED3BC /* tip_location_wood@2x.png in Resources */,
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */,
DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */,
DAD3126C15528C9C00A3F9ED /* TestFlight.plist in Resources */,
DAD3129015528D1600A3F9ED /* Localytics.plist in Resources */,
DA0A1D0515690A9A0092735D /* Default.png in Resources */,
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */,
DA0A1D0D15690AD40092735D /* tip_arrow_banana.png in Resources */,
DA0A1D0E15690AD40092735D /* tip_arrow_black.png in Resources */,
DA0A1D0F15690AD40092735D /* tip_arrow_gray.png in Resources */,
DA0A1D1015690AD40092735D /* tip_arrow_mercury.png in Resources */,
DA0A1D1115690AD40092735D /* tip_arrow_teal.png in Resources */,
DA0A1D1215690AD40092735D /* tip_arrow_wood.png in Resources */,
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */,
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */,
DACABB861572A2A7008BA211 /* tip_alert_black.png in Resources */,
DACABB871572A2A7008BA211 /* tip_alert_black@2x.png in Resources */,
DACABB881572A2A7008BA211 /* tip_basic_black.png in Resources */,
DACABB891572A2A7008BA211 /* tip_basic_black@2x.png in Resources */,
DACABB8C1572A4A5008BA211 /* tip_basic_black_top_right.png in Resources */,
DACABB8D1572A4A5008BA211 /* tip_basic_black_top_right@2x.png in Resources */,
DACABB901572B76A008BA211 /* tip_basic_black_top.png in Resources */,
DACABB911572B76A008BA211 /* tip_basic_black_top@2x.png in Resources */,
DAE4C969157E63BE00EFE047 /* avatar-0.png in Resources */,
DAE4C96A157E63BE00EFE047 /* avatar-0@2x.png in Resources */,
DAE4C96B157E63BE00EFE047 /* avatar-1.png in Resources */,
DAE4C96C157E63BE00EFE047 /* avatar-1@2x.png in Resources */,
DAE4C96D157E63BE00EFE047 /* avatar-2.png in Resources */,
DAE4C96E157E63BE00EFE047 /* avatar-2@2x.png in Resources */,
DAE4C96F157E63BE00EFE047 /* avatar-3.png in Resources */,
DAE4C970157E63BE00EFE047 /* avatar-3@2x.png in Resources */,
DAE4C971157E63BE00EFE047 /* avatar-4.png in Resources */,
DAE4C972157E63BE00EFE047 /* avatar-4@2x.png in Resources */,
DAE4C973157E63BE00EFE047 /* avatar-5.png in Resources */,
DAE4C974157E63BE00EFE047 /* avatar-5@2x.png in Resources */,
DAE4C975157E63BE00EFE047 /* avatar-6.png in Resources */,
DAE4C976157E63BE00EFE047 /* avatar-6@2x.png in Resources */,
DAE4C977157E63BE00EFE047 /* avatar-7.png in Resources */,
DAE4C978157E63BE00EFE047 /* avatar-7@2x.png in Resources */,
DAE4C979157E63BE00EFE047 /* avatar-8.png in Resources */,
DAE4C97A157E63BE00EFE047 /* avatar-8@2x.png in Resources */,
DAE4C97B157E63BE00EFE047 /* avatar-9.png in Resources */,
DAE4C97C157E63BE00EFE047 /* avatar-9@2x.png in Resources */,
DAE4C97D157E63BE00EFE047 /* avatar-10.png in Resources */,
DAE4C97E157E63BE00EFE047 /* avatar-10@2x.png in Resources */,
DAE4C97F157E63BE00EFE047 /* avatar-11.png in Resources */,
DAE4C980157E63BE00EFE047 /* avatar-11@2x.png in Resources */,
DAE4C981157E63BE00EFE047 /* avatar-12.png in Resources */,
DAE4C982157E63BE00EFE047 /* avatar-12@2x.png in Resources */,
DAE4C983157E63BE00EFE047 /* avatar-13.png in Resources */,
DAE4C984157E63BE00EFE047 /* avatar-13@2x.png in Resources */,
DAE4C985157E63BE00EFE047 /* avatar-14.png in Resources */,
DAE4C986157E63BE00EFE047 /* avatar-14@2x.png in Resources */,
DAE4C987157E63BE00EFE047 /* avatar-15.png in Resources */,
DAE4C988157E63BE00EFE047 /* avatar-15@2x.png in Resources */,
DAE4C989157E63BE00EFE047 /* avatar-16.png in Resources */,
DAE4C98A157E63BE00EFE047 /* avatar-16@2x.png in Resources */,
DAE4C98B157E63BE00EFE047 /* avatar-17.png in Resources */,
DAE4C98C157E63BE00EFE047 /* avatar-17@2x.png in Resources */,
DAE4C98D157E63BE00EFE047 /* avatar-18.png in Resources */,
DAE4C98E157E63BE00EFE047 /* avatar-18@2x.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3915,7 +4086,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = "/bin/bash -e";
shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --tags --always --dirty --long)\nbuild=${description%-g*} build=${build//-/.}\ntag=${description%%-*}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --always --dirty --long)\nbuild=${description%-g*} build=${build//-/.}\ntag=${description%%-*}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
showEnvVarsInLog = 0;
};
DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */ = {
@@ -3957,14 +4128,25 @@
DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
DAB8D46B15036BCF00CED3BC /* MPElementStoredEntity.m in Sources */,
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
DAB8D46D15036BCF00CED3BC /* MPElementEntity.m in Sources */,
DAB8D46E15036BCF00CED3BC /* MPElementGeneratedEntity.m in Sources */,
DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */,
DA40C2641586099E0079CE6E /* MPElementEntity.m in Sources */,
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */,
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA829E4D159847E0002417D3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA829E6115984813002417D3 /* UIFont+Replacement.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -4011,9 +4193,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DAFE4A1415039824003ABA7C /* NSObject_PearlExport.m in Sources */,
DAFE4A1615039824003ABA7C /* NSString_PearlNSArrayFormat.m in Sources */,
DAFE4A1815039824003ABA7C /* NSString_PearlSEL.m in Sources */,
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */,
DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */,
DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */,
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */,
DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */,
DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */,
@@ -4041,8 +4223,18 @@
DAFE4A5315039824003ABA7C /* PearlUIUtils.m in Sources */,
DAFE4A5515039824003ABA7C /* PearlValidatingTextField.m in Sources */,
DAFE4A5715039824003ABA7C /* PearlWebViewController.m in Sources */,
DAFE4A5915039824003ABA7C /* UIImage_PearlScaling.m in Sources */,
DAFE4A5915039824003ABA7C /* UIImage+PearlScaling.m in Sources */,
DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */,
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */,
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */,
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */,
DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */,
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */,
DAFE4A63150399FF003ABA82 /* UIControl+PearlBlocks.m in Sources */,
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */,
DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */,
DAFE4A63150399FF003ABA8E /* UIScrollView+PearlFlashingIndicators.m in Sources */,
DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -4129,7 +4321,8 @@
CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Maarten Billemont (DWGU95U4ZD)";
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -4169,7 +4362,7 @@
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "FCADE039-9917-4715-9172-ABBAC9806763";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -4187,7 +4380,8 @@
CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont";
COPY_PHASE_STRIP = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -4226,10 +4420,11 @@
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "1CC091A5-5890-4382-A72F-1397B66FE136";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = AdHoc;
};
@@ -4277,6 +4472,27 @@
};
name = AdHoc;
};
DA829E5A159847E0002417D3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
};
name = Debug;
};
DA829E5B159847E0002417D3 /* AdHoc */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
};
name = AdHoc;
};
DA829E5C159847E0002417D3 /* AppStore */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
};
name = AppStore;
};
DA95D5A514DF063C008D1B94 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -4310,7 +4526,8 @@
CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont";
COPY_PHASE_STRIP = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -4349,10 +4566,11 @@
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "7BF77F10-5D46-4762-B27F-55A39E089052";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = AppStore;
};
@@ -4569,6 +4787,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = AdHoc;
};
DA829E59159847E0002417D3 /* Build configuration list for PBXNativeTarget "FontReplacer" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA829E5A159847E0002417D3 /* Debug */,
DA829E5B159847E0002417D3 /* AdHoc */,
DA829E5C159847E0002417D3 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = AdHoc;
};
DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -63,14 +63,23 @@
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Production"
buildConfiguration = "AppStore"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Production"
buildConfiguration = "AppStore"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -40,11 +40,11 @@
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
buildConfiguration = "AppStore"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
@@ -57,12 +57,6 @@
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.coredata.ubiquity.logLevel 3"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
@@ -75,7 +69,7 @@
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "AdHoc"
buildConfiguration = "Debug"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -10,13 +10,10 @@
@interface MPAppDelegate_Shared (Key)
- (void)loadStoredKey;
- (IBAction)signOut:(id)sender;
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
- (void)signOut;
- (BOOL)tryMasterPassword:(NSString *)tryPassword;
- (void)updateKey:(NSData *)key;
- (void)forgetKey;
- (NSData *)keyWithLength:(NSUInteger)keyLength;
- (void)storeSavedKeyFor:(MPUserEntity *)user;
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
@end

View File

@@ -6,153 +6,169 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPConfig.h"
#import <Crashlytics/Crashlytics.h>
#import "MPAppDelegate_Key.h"
#import "MPElementEntity.h"
#import "MPAppDelegate_Store.h"
#import "LocalyticsSession.h"
@implementation MPAppDelegate_Shared (Key)
static NSDictionary *keyQuery() {
static NSDictionary *MPKeyQuery = nil;
if (!MPKeyQuery)
MPKeyQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"Saved Master Password", (__bridge id)kSecAttrService,
@"default", (__bridge id)kSecAttrAccount,
nil]
matches:nil];
return MPKeyQuery;
static NSDictionary *keyQuery(MPUserEntity *user) {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"Saved Master Password", (__bridge id)kSecAttrService,
user.name, (__bridge id)kSecAttrAccount,
nil]
matches:nil];
}
static NSDictionary *keyIDQuery() {
static NSDictionary *MPKeyIDQuery = nil;
if (!MPKeyIDQuery)
MPKeyIDQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"Master Password Check", (__bridge id)kSecAttrService,
@"default", (__bridge id)kSecAttrAccount,
nil]
matches:nil];
return MPKeyIDQuery;
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (key)
inf(@"Found key in keychain for: %@", user.userID);
else {
user.saveKey = NO;
inf(@"No key found in keychain for: %@", user.userID);
}
return key;
}
- (void)forgetKey {
inf(@"Deleting key and ID from keychain.");
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
inf(@"Removed key from keychain.");
if ([PearlKeyChain deleteItemForQuery:keyIDQuery()] != errSecItemNotFound)
inf(@"Removed key ID from keychain.");
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
#endif
}
- (void)storeSavedKeyFor:(MPUserEntity *)user {
- (IBAction)signOut:(id)sender {
if (user.saveKey) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
[MPConfig get].saveKey = [NSNumber numberWithBool:NO];
[self updateKey:nil];
}
if (![existingKey isEqualToData:self.key]) {
inf(@"Saving key in keychain for: %@", user.userID);
- (void)loadStoredKey {
if ([[MPConfig get].saveKey boolValue]) {
// Key is stored in keychain. Load it.
[self updateKey:[PearlKeyChain dataOfItemForQuery:keyQuery()]];
inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing");
} else {
// Key should not be stored in keychain. Delete it.
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
inf(@"Removed key from keychain.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
#endif
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
nil]];
}
}
}
- (BOOL)tryMasterPassword:(NSString *)tryPassword {
if (![tryPassword length])
return NO;
NSData *tryKey = keyForPassword(tryPassword);
NSData *tryKeyID = keyIDForKey(tryKey);
NSData *keyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
inf(@"Key ID known? %@.", keyID? @"YES": @"NO");
if (keyID)
// A key ID is known -> a password is set.
// Make sure the user's entered password matches it.
if (![keyID isEqual:tryKeyID]) {
wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [keyID encodeHex], [tryKeyID encodeHex]);
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
if (result == noErr || result == errSecItemNotFound) {
user.saveKey = NO;
if (result == noErr) {
inf(@"Removed key from keychain for: %@", user.userID);
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointForgetSavedKey];
#endif
}
}
}
- (void)signOut {
if (self.key)
self.key = nil;
if (self.activeUser) {
self.activeUser = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
}
}
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
NSData *tryKey = nil;
// Method 1: When the user has no keyID set, set a new key from the given master password.
if (!user.keyID) {
if ([password length])
if ((tryKey = keyForPassword(password, user.name))) {
user.keyID = keyIDForKey(tryKey);
[[MPAppDelegate_Shared get] saveContext];
}
}
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
if (!user.saveKey)
// Key should not be stored in keychain. Delete it.
[self forgetSavedKeyFor:user];
else
if (!tryKey) {
// Key should be saved in keychain. Load it.
if ((tryKey = [self loadSavedKeyFor:user]))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
inf(@"Saved password doesn't match keyID for: %@", user.userID);
tryKey = nil;
[self forgetSavedKeyFor:user];
}
}
// Method 3: Check the given master password string.
if (!tryKey) {
if ([password length])
if ((tryKey = keyForPassword(password, user.name)))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
tryKey = nil;
}
}
// No more methods left, fail if key still not known.
if (!tryKey) {
if (password) {
inf(@"Login failed for: %@", user.userID);
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
#endif
return NO;
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed attributes:nil];
}
return NO;
}
inf(@"Logged in: %@", user.userID);
if (![self.key isEqualToData:tryKey]) {
self.key = tryKey;
[self storeSavedKeyFor:user];
}
@try {
if ([[MPiOSConfig get].sendInfo boolValue]) {
[TestFlight addCustomEnvironmentInformation:user.userID forKey:@"username"];
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
}
}
@catch (id exception) {
err(@"While setting username: %@", exception);
}
user.lastUsed = [NSDate date];
self.activeUser = user;
[[MPAppDelegate_Shared get] saveContext];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered];
[TestFlight passCheckpoint:MPCheckpointSignedIn];
#endif
[self updateKey:tryKey];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignedIn
attributes:nil];
return YES;
}
- (void)updateKey:(NSData *)key {
if (self.key != key) {
self.key = key;
if (key)
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
else
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
}
if (self.key) {
self.keyID = keyIDForKey(self.key);
NSData *existingKeyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
if (![existingKeyID isEqualToData:self.keyID]) {
inf(@"Updating key ID in keychain.");
[PearlKeyChain addOrUpdateItemForQuery:keyIDQuery()
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.keyID, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
#endif
nil]];
}
if ([[MPConfig get].saveKey boolValue]) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery()];
if (![existingKey isEqualToData:self.key]) {
inf(@"Updating key in keychain.");
[PearlKeyChain addOrUpdateItemForQuery:keyQuery()
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
#endif
nil]];
}
}
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
#endif
}
}
- (NSData *)keyWithLength:(NSUInteger)keyLength {
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];
}
@end

View File

@@ -6,14 +6,17 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPEntities.h"
#if TARGET_OS_IPHONE
@interface MPAppDelegate_Shared : PearlAppDelegate
#else
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
#endif
@property (strong, nonatomic) NSData *key;
@property (strong, nonatomic) NSData *keyID;
@property (strong, nonatomic) MPUserEntity *activeUser;
@property (strong, nonatomic) NSData *key;
+ (MPAppDelegate_Shared *)get;

View File

@@ -11,10 +11,10 @@
@implementation MPAppDelegate_Shared
@synthesize key;
@synthesize keyID;
@synthesize activeUser;
+ (MPAppDelegate_Shared *)get {
#if TARGET_OS_IPHONE
return (MPAppDelegate_Shared *)[UIApplication sharedApplication].delegate;
#elif defined (__MAC_OS_X_VERSION_MIN_REQUIRED)

View File

@@ -18,7 +18,7 @@ typedef enum {
MPImportResultInternalError,
} MPImportResult;
@interface MPAppDelegate_Shared (Store) <UbiquityStoreManagerDelegate>
@interface MPAppDelegate_Shared (Store)<UbiquityStoreManagerDelegate>
+ (NSManagedObjectContext *)managedObjectContext;
+ (NSManagedObjectModel *)managedObjectModel;
@@ -27,7 +27,6 @@ typedef enum {
- (UbiquityStoreManager *)storeManager;
- (void)saveContext;
- (void)printStore;
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation;

View File

@@ -7,80 +7,75 @@
//
#import "MPAppDelegate_Store.h"
#import "MPElementEntity.h"
#import "MPConfig.h"
#import "LocalyticsSession.h"
@implementation MPAppDelegate_Shared (Store)
static NSDateFormatter *rfc3339DateFormatter = nil;
#pragma mark - Core Data setup
+ (NSManagedObjectContext *)managedObjectContext {
return [[self get] managedObjectContext];
}
+ (NSManagedObjectModel *)managedObjectModel {
return [[self get] managedObjectModel];
}
- (NSManagedObjectModel *)managedObjectModel {
static NSManagedObjectModel *managedObjectModel = nil;
if (managedObjectModel)
return managedObjectModel;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"];
return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
- (NSManagedObjectContext *)managedObjectContext {
static NSManagedObjectContext *managedObjectContext = nil;
if (managedObjectContext)
return managedObjectContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
assert(coordinator);
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext performBlockAndWait:^{
managedObjectContext.persistentStoreCoordinator = coordinator;
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
managedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
}];
return managedObjectContext;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// Start loading the store.
[self storeManager];
// Wait until the storeManager is ready.
for(__block BOOL isReady = [self storeManager].isReady; !isReady;)
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
isReady = [self storeManager].isReady;
});
assert([self storeManager].isReady);
while (![self storeManager].isReady)
[NSThread sleepForTimeInterval:0.1];
return [self storeManager].persistentStoreCoordinator;
}
- (UbiquityStoreManager *)storeManager {
static UbiquityStoreManager *storeManager = nil;
if (storeManager)
return storeManager;
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
#if TARGET_OS_IPHONE
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSPersistentStoreFileProtectionKey]
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSPersistentStoreFileProtectionKey]
#else
additionalStoreOptions:nil
#endif
];
];
storeManager.delegate = self;
#ifdef DEBUG
storeManager.hardResetEnabled = YES;
@@ -88,9 +83,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) {
[storeManager checkiCloudStatus];
}];
usingBlock:^(NSNotification *note) {
[storeManager checkiCloudStatus];
}];
#else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
object:[NSApplication sharedApplication] queue:nil
@@ -101,9 +96,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) {
[self saveContext];
}];
usingBlock:^(NSNotification *note) {
[self saveContext];
}];
#else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
object:[NSApplication sharedApplication] queue:nil
@@ -111,97 +106,56 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[self saveContext];
}];
#endif
return storeManager;
}
- (void)saveContext {
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if ([self.managedObjectContext hasChanges])
if (![self.managedObjectContext save:&error])
err(@"While saving context: %@", error);
}];
}
- (void)printStore {
if (![self managedObjectModel] || ![self managedObjectContext]) {
trc(@"Not printing store: store not initialized.");
return;
}
[self.managedObjectContext performBlock:^{
trc(@"=== All entities ===");
for(NSEntityDescription *entity in [[self managedObjectModel] entities]) {
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:entity];
NSError *error;
NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:&error];
for(NSManagedObject *o in results) {
if ([o isKindOfClass:[MPElementEntity class]]) {
MPElementEntity *e = (MPElementEntity *)o;
trc(@"For descriptor: %@, found: %@: %@ (%@)", entity.name, [o class], e.name, e.keyID);
} else {
trc(@"For descriptor: %@, found: %@", entity.name, [o class]);
}
}
}
trc(@"---");
if ([MPAppDelegate_Shared get].keyID) {
trc(@"=== Known sites ===");
NSFetchRequest *fetchRequest = [[self managedObjectModel]
fetchRequestFromTemplateWithName:@"MPElements"
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
@"", @"query",
[MPAppDelegate_Shared get].keyID, @"keyID",
nil]];
[fetchRequest setSortDescriptors:
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
NSError *error = nil;
for (MPElementEntity *e in [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error]) {
trc(@"Found site: %@ (%@): %@", e.name, e.keyID, e);
}
trc(@"---");
} else
trc(@"Not printing sites: master password not set.");
err(@"While saving context: %@", error);
}];
}
#pragma mark - UbiquityStoreManagerDelegate
- (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm {
return self.managedObjectContext;
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
dbg(@"[StoreManager] %@", message);
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
// manager.iCloudEnabled is more reliable (eg. iOS tampers with didSwitch a bit)
// manager.iCloudEnabled is more reliable (eg. iOS' MPAppDelegate tampers with didSwitch a bit)
iCloudEnabled = manager.iCloudEnabled;
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:iCloudEnabled? MPTestFlightCheckpointCloudEnabled: MPTestFlightCheckpointCloudDisabled];
#endif
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause context:(id)context {
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:PearlString(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)];
#endif
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause
context:(id)context {
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:PearlString(@"MPCheckpointMPErrorUbiquity_%d", cause)];
#endif
switch (cause) {
case UbiquityStoreManagerErrorCauseDeleteStore:
case UbiquityStoreManagerErrorCauseDeleteLogs:
@@ -209,21 +163,27 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
case UbiquityStoreManagerErrorCauseClearStore:
break;
case UbiquityStoreManagerErrorCauseOpenLocalStore: {
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointLocalStoreIncompatible];
#endif
wrn(@"Local store could not be opened, resetting it.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible
attributes:nil];
manager.hardResetEnabled = YES;
[manager hardResetLocalStorage];
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."];
return;
}
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointCloudStoreIncompatible];
#endif
wrn(@"iCloud store could not be opened, resetting it.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible
attributes:nil];
manager.hardResetEnabled = YES;
[manager hardResetCloudStorage];
break;
@@ -233,49 +193,39 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#pragma mark - Import / Export
- (void)loadRFC3339DateFormatter {
if (rfc3339DateFormatter)
return;
rfc3339DateFormatter = [NSDateFormatter new];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
}
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
[self loadRFC3339DateFormatter];
inf(@"Importing sites.");
static NSRegularExpression *headerPattern, *sitePattern;
__autoreleasing NSError *error;
if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc]
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:0 error:&error];
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:0 error:&error];
if (error)
err(@"Error loading the header pattern: %@", error);
err(@"Error loading the header pattern: %@", error);
}
if (!sitePattern) {
sitePattern = [[NSRegularExpression alloc]
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
options:0 error:&error];
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
options:0 error:&error];
if (error)
err(@"Error loading the site pattern: %@", error);
err(@"Error loading the site pattern: %@", error);
}
if (!headerPattern || !sitePattern)
return MPImportResultInternalError;
NSString *keyIDHex = nil;
BOOL headerStarted = NO, headerEnded = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set];
NSData *key = nil;
NSString *keyIDHex = nil, *userName = nil;
MPUserEntity *user = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
for(NSString *importedSiteLine in importedSiteLines) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header
if (!headerStarted) {
@@ -289,97 +239,131 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
headerEnded = YES;
continue;
}
// Header
if ([headerPattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
err(@"Invalid header format in line: %@", importedSiteLine);
return MPImportResultMalformedInput;
}
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([key isEqualToString:@"Key ID"]) {
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]])
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
userName = headerValue;
key = keyForPassword(password, userName);
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
}
if ([headerName isEqualToString:@"Key ID"]) {
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]])
return MPImportResultInvalidPassword;
}
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
}
continue;
}
if (!headerEnded)
continue;
if (!keyIDHex)
if (!keyIDHex || ![userName length])
return MPImportResultMalformedInput;
if (![importedSiteLine length])
continue;
// Site
if ([sitePattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
err(@"Invalid site format in line: %@", importedSiteLine);
return MPImportResultMalformedInput;
}
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
// Find existing site.
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND keyID == %@", name, keyIDHex];
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error)
if (user) {
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"Couldn't search existing sites: %@", error);
if (!existingSites)
return MPImportResultInternalError;
[elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
if (!existingSites) {
err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID);
return MPImportResultInternalError;
}
[elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
}
}
inf(@"Importing %u sites, deleting %u sites, for user: %@",
[importedSiteElements count], [elementsToDelete count], [MPUserEntity idFor:userName]);
// Ask for confirmation to import these sites.
if (!confirmation([importedSiteElements count], [elementsToDelete count]))
if (!confirmation([importedSiteElements count], [elementsToDelete count])) {
inf(@"Import cancelled.");
return MPImportResultCancelled;
}
// Delete existing sites.
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
[self.managedObjectContext deleteObject:obj];
}];
[self saveContext];
// Import new sites.
if (!user) {
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:self.managedObjectContext];
user.name = userName;
user.keyID = [keyIDHex decodeHex];
}
for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]];
NSInteger uses = [[siteElements objectAtIndex:1] integerValue];
MPElementType type = (unsigned)[[siteElements objectAtIndex:2] integerValue];
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
NSString *name = [siteElements objectAtIndex:3];
NSString *exportContent = [siteElements objectAtIndex:4];
// Create new site.
inf(@"Importing site: name=%@, lastUsed=%@, uses=%d, type=%u, keyID=%@", name, lastUsed, uses, type, keyIDHex);
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:self.managedObjectContext];
element.name = name;
element.keyID = [keyIDHex decodeHex];
element.type = type;
element.uses = uses;
element.lastUsed = [lastUsed timeIntervalSinceReferenceDate];
element.name = name;
element.user = user;
element.type = type;
element.uses = uses;
element.lastUsed = lastUsed;
if ([exportContent length])
[element importContent:exportContent];
if (clearText)
[element importClearTextContent:exportContent usingKey:key];
else
[element importProtectedContent:exportContent];
}
[self saveContext];
inf(@"Import completed successfully.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported];
[TestFlight passCheckpoint:MPCheckpointSitesImported];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported
attributes:nil];
return MPImportResultSuccess;
}
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
[self loadRFC3339DateFormatter];
inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", self.activeUser.userID);
// Header.
NSMutableString *export = [NSMutableString new];
[export appendFormat:@"# Master Password site export\n"];
@@ -390,8 +374,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[export appendFormat:@"# \n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# Key ID: %@\n", [self.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]];
[export appendFormat:@"# User Name: %@\n", self.activeUser.name];
[export appendFormat:@"# Key ID: %@\n", [self.activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
if (showPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"];
else
@@ -400,39 +385,34 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[export appendFormat:@"#\n"];
[export appendFormat:@"# Last Times Password Site\tSite\n"];
[export appendFormat:@"# used used type name\tpassword\n"];
// Sites.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", self.keyID];
__autoreleasing NSError *error = nil;
NSArray *elements = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"Error fetching sites for export: %@", error);
for (MPElementEntity *element in elements) {
NSTimeInterval lastUsed = element.lastUsed;
int16_t uses = element.uses;
MPElementType type = (unsigned)element.type;
NSString *name = element.name;
for (MPElementEntity *element in self.activeUser.elements) {
NSDate *lastUsed = element.lastUsed;
NSUInteger uses = element.uses;
MPElementType type = element.type;
NSString *name = element.name;
NSString *content = nil;
// Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) {
if (showPasswords)
content = element.content;
else if (type & MPElementFeatureExportContent)
content = element.exportContent;
else
if (type & MPElementFeatureExportContent)
content = element.exportContent;
}
[export appendFormat:@"%@ %8d %8d %20s\t%@\n",
[rfc3339DateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:lastUsed]], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content? content: @""];
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
? content: @""];
}
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported];
[TestFlight passCheckpoint:MPCheckpointSitesExported];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesExported attributes:nil];
return export;
}

View File

@@ -6,10 +6,11 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "Pearl.h"
@interface MPConfig : PearlConfig
@property (nonatomic, retain) NSNumber *saveKey;
@property (nonatomic, retain) NSNumber *rememberKey;
@property (nonatomic, retain) NSNumber *rememberLogin;
@property (nonatomic, retain) NSNumber *iCloud;
@property (nonatomic, retain) NSNumber *iCloudDecided;

View File

@@ -6,33 +6,31 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPConfig.h"
#import "MPAppDelegate.h"
@implementation MPConfig
@dynamic saveKey, rememberKey, iCloud, iCloudDecided;
@dynamic rememberLogin, iCloud, iCloudDecided;
- (id)init {
if(!(self = [super init]))
if (!(self = [super init]))
return nil;
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(saveKey)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKey)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
nil]];
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
nil]];
self.delegate = [MPAppDelegate get];
return self;
}
+ (MPConfig *)get {
return (MPConfig *)[super get];
}

View File

@@ -1,27 +1,23 @@
//
// MPElementEntity.h
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 02/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPUserEntity;
@interface MPElementEntity : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSData *keyID;
@property (nonatomic, assign) int16_t type;
@property (nonatomic, assign) int16_t uses;
@property (nonatomic, assign) NSTimeInterval lastUsed;
@property (nonatomic, retain, readonly) id content;
- (int16_t)use;
- (NSString *)exportContent;
- (void)importContent:(NSString *)content;
@property (nonatomic, retain) id content;
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) MPUserEntity *user;
@end

View File

@@ -1,51 +1,22 @@
//
// MPElementEntity.m
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 02/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPElementEntity.h"
#import "MPUserEntity.h"
@implementation MPElementEntity
@dynamic name;
@dynamic keyID;
@dynamic type;
@dynamic uses;
@dynamic content;
@dynamic lastUsed;
- (int16_t)use {
self.lastUsed = [[NSDate date] timeIntervalSinceReferenceDate];
return ++self.uses;
}
- (id)content {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
}
- (NSString *)exportContent {
return nil;
}
- (void)importContent:(NSString *)content {
}
- (NSString *)description {
return PearlString(@"%@:%@", [self class], [self name]);
}
- (NSString *)debugDescription {
return PearlString(@"{%@: name=%@, keyID=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.keyID, self.type, self.uses, self.lastUsed);
}
@dynamic name;
@dynamic type_;
@dynamic uses_;
@dynamic user;
@end

View File

@@ -1,8 +1,8 @@
//
// MPElementGeneratedEntity.h
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 16/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, assign) int32_t counter;
@property (nonatomic, retain) NSNumber * counter_;
@end

View File

@@ -1,31 +1,16 @@
//
// MPElementGeneratedEntity.m
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 16/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPElementGeneratedEntity.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
@implementation MPElementGeneratedEntity
@dynamic counter;
- (id)content {
if (!(self.type & MPElementTypeClassGenerated)) {
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
return nil;
}
if (![self.name length])
return nil;
return MPCalculateContent((unsigned)self.type, self.name, [MPAppDelegate get].key, self.counter);
}
@dynamic counter_;
@end

View File

@@ -1,8 +1,8 @@
//
// MPElementStoredEntity.h
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 02/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@@ -13,6 +13,6 @@
@interface MPElementStoredEntity : MPElementEntity
@property (nonatomic, retain, readwrite) id content;
@property (nonatomic, retain) id contentObject;
@end

View File

@@ -1,76 +1,16 @@
//
// MPElementStoredEntity.m
// MasterPassword
// MasterPassword-iOS
//
// Created by Maarten Billemont on 02/01/12.
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPElementStoredEntity.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
@interface MPElementStoredEntity ()
@property (nonatomic, retain, readwrite) id contentObject;
@end
@implementation MPElementStoredEntity
@dynamic contentObject;
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount,
nil]
matches:nil];
}
- (id)content {
assert(self.type & MPElementTypeClassStored);
NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate)
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content {
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
nil]];
self.contentObject = nil;
} else
self.contentObject = encryptedContent;
}
- (NSString *)exportContent {
return [self.contentObject encodeBase64];
}
- (void)importContent:(NSString *)content {
self.contentObject = [content decodeBase64];
}
@end

View File

@@ -0,0 +1,44 @@
//
// MPElementEntities.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPElementEntity.h"
#import "MPElementStoredEntity.h"
#import "MPElementGeneratedEntity.h"
#import "MPUserEntity.h"
#define MPAvatarCount 19
@interface MPElementEntity (MP)
@property (assign) MPElementType type;
@property (assign) NSUInteger uses;
- (NSUInteger)use;
- (NSString *)exportContent;
- (void)importProtectedContent:(NSString *)protectedContent;
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key;
@end
@interface MPElementGeneratedEntity (MP)
@property (assign) NSUInteger counter;
@end
@interface MPUserEntity (MP)
@property (assign) NSUInteger avatar;
@property (assign) BOOL saveKey;
@property (assign) MPElementType defaultType;
@property (readonly) NSString *userID;
+ (NSString *)idFor:(NSString *)userName;
@end

218
MasterPassword/MPEntities.m Normal file
View File

@@ -0,0 +1,218 @@
//
// MPElementEntities.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPEntities.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPUserEntity.h"
@implementation MPElementEntity (MP)
- (MPElementType)type {
return (MPElementType)[self.type_ unsignedIntegerValue];
}
- (void)setType:(MPElementType)aType {
self.type_ = PearlUnsignedInteger(aType);
}
- (NSUInteger)uses {
return [self.uses_ unsignedIntegerValue];
}
- (void)setUses:(NSUInteger)anUses {
self.uses_ = PearlUnsignedInteger(anUses);
}
- (NSUInteger)use {
self.lastUsed = [NSDate date];
return ++self.uses;
}
- (id)content {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
}
- (NSString *)exportContent {
return nil;
}
- (void)importProtectedContent:(NSString *)content {
}
- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key {
}
- (NSString *)description {
return PearlString(@"%@:%@", [self class], [self name]);
}
- (NSString *)debugDescription {
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed);
}
@end
@implementation MPElementGeneratedEntity (MP)
- (NSUInteger)counter {
return [self.counter_ unsignedIntegerValue];
}
- (void)setCounter:(NSUInteger)aCounter {
self.counter_ = PearlUnsignedInteger(aCounter);
}
- (id)content {
if (!(self.type & MPElementTypeClassGenerated)) {
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
return nil;
}
if (![self.name length])
return nil;
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter);
}
@end
@implementation MPElementStoredEntity (MP)
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount,
nil]
matches:nil];
}
- (id)content {
return [self contentUsingKey:[MPAppDelegate get].key];
}
- (void)setContent:(id)content {
[self setContent:content usingKey:[MPAppDelegate get].key];
}
- (id)contentUsingKey:(NSData *)key {
assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate)
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content usingKey:(NSData *)key {
assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
nil]];
self.contentObject = nil;
} else
self.contentObject = encryptedContent;
}
- (NSString *)exportContent {
return [self.contentObject encodeBase64];
}
- (void)importProtectedContent:(NSString *)protectedContent {
self.contentObject = [protectedContent decodeBase64];
}
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key {
[self setContent:clearContent usingKey:key];
}
@end
@implementation MPUserEntity (MP)
- (NSUInteger)avatar {
return [self.avatar_ unsignedIntegerValue];
}
- (void)setAvatar:(NSUInteger)anAvatar {
self.avatar_ = PearlUnsignedInteger(anAvatar);
}
- (BOOL)saveKey {
return [self.saveKey_ boolValue];
}
- (void)setSaveKey:(BOOL)aSaveKey {
self.saveKey_ = [NSNumber numberWithBool:aSaveKey];
}
- (MPElementType)defaultType {
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
}
- (NSString *)userID {
return [MPUserEntity idFor:self.name];
}
- (void)setDefaultType:(MPElementType)aDefaultType {
self.defaultType_ = PearlUnsignedInteger(aDefaultType);
}
+ (NSString *)idFor:(NSString *)userName {
return [[userName hashWith:PearlHashSHA1] encodeHex];
}
@end

View File

@@ -18,68 +18,71 @@ typedef enum {
typedef enum {
/** Generate the password. */
MPElementTypeClassGenerated = 1 << 4,
MPElementTypeClassGenerated = 1 << 4,
/** Store the password. */
MPElementTypeClassStored = 1 << 5,
MPElementTypeClassStored = 1 << 5,
} MPElementTypeClass;
typedef enum {
/** Export the key-protected content data. */
MPElementFeatureExportContent = 1 << 10,
MPElementFeatureExportContent = 1 << 10,
/** Never export content. */
MPElementFeatureDevicePrivate = 1 << 11,
MPElementFeatureDevicePrivate = 1 << 11,
} MPElementFeature;
typedef enum {
MPElementTypeGeneratedLong = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x2 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedBasic = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
} MPElementType;
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
#define MPTestFlightCheckpointResetPasswordCounter @"MPTestFlightCheckpointResetPasswordCounter"
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
#define MPTestFlightCheckpointSelectType @"MPTestFlightCheckpointSelectType_%@"
#define MPTestFlightCheckpointSelectElement @"MPTestFlightCheckpointSelectElement"
#define MPTestFlightCheckpointDeleteElement @"MPTestFlightCheckpointDeleteElement"
#define MPTestFlightCheckpointCancelSearch @"MPTestFlightCheckpointCancelSearch"
#define MPTestFlightCheckpointExternalLink @"MPTestFlightCheckpointExternalLink"
#define MPTestFlightCheckpointLaunched @"MPTestFlightCheckpointLaunched"
#define MPTestFlightCheckpointActivated @"MPTestFlightCheckpointActivated"
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
#define MPTestFlightCheckpointMPEntered @"MPTestFlightCheckpointMPEntered"
#define MPTestFlightCheckpointLocalStoreIncompatible @"MPTestFlightCheckpointLocalStoreIncompatible"
#define MPTestFlightCheckpointCloudStoreIncompatible @"MPTestFlightCheckpointCloudStoreIncompatible"
#define MPTestFlightCheckpointSetKey @"MPTestFlightCheckpointSetKey"
#define MPTestFlightCheckpointCloudEnabled @"MPTestFlightCheckpointCloudEnabled"
#define MPTestFlightCheckpointCloudDisabled @"MPTestFlightCheckpointCloudDisabled"
#define MPTestFlightCheckpointSitesImported @"MPTestFlightCheckpointSitesImported"
#define MPTestFlightCheckpointSitesExported @"MPTestFlightCheckpointSitesExported"
#define MPCheckpointAction @"MPCheckpointAction"
#define MPCheckpointHelpChapter @"MPCheckpointHelpChapter"
#define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard"
#define MPCheckpointResetPasswordCounter @"MPCheckpointResetPasswordCounter"
#define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter"
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
#define MPCheckpointUseType @"MPCheckpointUseType"
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
#define MPCheckpointCancelSearch @"MPCheckpointCancelSearch"
#define MPCheckpointExternalLink @"MPCheckpointExternalLink"
#define MPCheckpointLaunched @"MPCheckpointLaunched"
#define MPCheckpointActivated @"MPCheckpointActivated"
#define MPCheckpointDeactivated @"MPCheckpointDeactivated"
#define MPCheckpointTerminated @"MPCheckpointTerminated"
#define MPCheckpointShowGuide @"MPCheckpointShowGuide"
#define MPCheckpointForgetSavedKey @"MPCheckpointForgetSavedKey"
#define MPCheckpointChangeMP @"MPCheckpointChangeMP"
#define MPCheckpointLocalStoreIncompatible @"MPCheckpointLocalStoreIncompatible"
#define MPCheckpointCloudStoreIncompatible @"MPCheckpointCloudStoreIncompatible"
#define MPCheckpointSignInFailed @"MPCheckpointSignInFailed"
#define MPCheckpointSignedIn @"MPCheckpointSignedIn"
#define MPCheckpointConfig @"MPCheckpointConfig"
#define MPCheckpointCloud @"MPCheckpointCloud"
#define MPCheckpointCloudEnabled @"MPCheckpointCloudEnabled"
#define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
#define MPCheckpointSitesImported @"MPCheckpointSitesImported"
#define MPCheckpointSitesExported @"MPCheckpointSitesExported"
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
#define MPNotificationKeySet @"MPNotificationKeySet"
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
#define MPNotificationSignedIn @"MPNotificationKeySet"
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationElementUsed @"MPNotificationElementUsed"
NSData *keyForPassword(NSString *password);
NSData *keyIDForPassword(NSString *password);
NSData *keyIDForKey(NSData *key);
NSData *keyForPassword(NSString *password, NSString *username);
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
NSData *keyIDForPassword(NSString *password, NSString *username);
NSData *keyIDForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type);
NSString *NSStringShortFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);

View File

@@ -6,158 +6,216 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPTypes.h"
#import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h"
#import "MPEntities.h"
#define MP_salt nil
#define MP_N 16384
#define MP_N 131072
#define MP_r 8
#define MP_p 1
#define MP_dkLen 64
#define MP_hash PearlDigestSHA256
#define MP_hash PearlHashSHA256
NSData *keyForPassword(NSString *password) {
NSData *keyForPassword(NSString *password, NSString *username) {
uint32_t nusernameLength = htonl(username.length);
NSDate *start = [NSDate date];
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
trc(@"Password: %@ derives to key ID: %@", password, [keyIDForKey(key) encodeHex]);
usingSalt:[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nusernameLength
length:sizeof(nusernameLength)],
[username dataUsingEncoding:NSUTF8StringEncoding],
nil] N:MP_N r:MP_r p:MP_p];
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2f)", username, password, [keyIDForKey(key) encodeHex], -[start timeIntervalSinceNow]);
return key;
}
NSData *keyIDForPassword(NSString *password) {
return keyIDForKey(keyForPassword(password));
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) {
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
}
NSData *keyIDForPassword(NSString *password, NSString *username) {
return keyIDForKey(keyForPassword(password, username));
}
NSData *keyIDForKey(NSData *key) {
return [key hashWith:MP_hash];
}
NSString *NSStringFromMPElementType(MPElementType type) {
if (!type)
return nil;
switch (type) {
case MPElementTypeGeneratedMaximum:
return @"Maximum Security Password";
case MPElementTypeGeneratedLong:
return @"Long Password";
case MPElementTypeGeneratedMedium:
return @"Medium Password";
case MPElementTypeGeneratedShort:
return @"Short Password";
case MPElementTypeGeneratedBasic:
return @"Basic Password";
case MPElementTypeGeneratedPIN:
return @"PIN";
case MPElementTypeStoredPersonal:
return @"Personal Password";
case MPElementTypeStoredDevicePrivate:
return @"Device Private Password";
default:
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type];
Throw(@"Type not supported: %d", type);
}
}
NSString *NSStringShortFromMPElementType(MPElementType type) {
if (!type)
return nil;
switch (type) {
case MPElementTypeGeneratedMaximum:
return @"Maximum";
case MPElementTypeGeneratedLong:
return @"Long";
case MPElementTypeGeneratedMedium:
return @"Medium";
case MPElementTypeGeneratedShort:
return @"Short";
case MPElementTypeGeneratedBasic:
return @"Basic";
case MPElementTypeGeneratedPIN:
return @"PIN";
case MPElementTypeStoredPersonal:
return @"Personal";
case MPElementTypeStoredDevicePrivate:
return @"Device";
default:
Throw(@"Type not supported: %d", type);
}
}
Class ClassFromMPElementType(MPElementType type) {
if (!type)
return nil;
switch (type) {
case MPElementTypeGeneratedMaximum:
return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedLong:
return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedMedium:
return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedShort:
return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedBasic:
return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedPIN:
return [MPElementGeneratedEntity class];
case MPElementTypeStoredPersonal:
return [MPElementStoredEntity class];
case MPElementTypeStoredDevicePrivate:
return [MPElementStoredEntity class];
default:
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type];
Throw(@"Type not supported: %d", type);
}
}
NSString *ClassNameFromMPElementType(MPElementType type) {
return NSStringFromClass(ClassFromMPElementType(type));
}
static NSDictionary *MPTypes_ciphers = nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter) {
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
if (!(type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
return nil;
}
if (!name) {
if (!name.length) {
err(@"Missing name.");
return nil;
}
if (!key) {
err(@"Key not set.");
if (!key.length) {
err(@"Missing key.");
return nil;
}
uint32_t salt = (unsigned)counter;
if (!counter)
// Counter unset, go into OTP mode.
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
salt = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
// Counter unset, go into OTP mode.
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
if (MPTypes_ciphers == nil)
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
withExtension:@"plist"]];
// Determine the seed whose bytes will be used for calculating a password: sha1(name . '\0' . key . '\0' . salt)
uint32_t nsalt = htonl(salt);
trc(@"seed from: sha1(%@, %@, %u)", name, key, nsalt);
NSData *seed = [[NSData dataByConcatenatingWithDelimitor:'\0' datas:
[name dataUsingEncoding:NSUTF8StringEncoding],
key,
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)],
nil] hashWith:PearlDigestSHA1];
// Determine the seed whose bytes will be used for calculating a password
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter);
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
NSData *seed = [[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
[name dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
nil]
hmacWith:PearlHashSHA256 key:key];
trc(@"seed is: %@", seed);
const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte.
assert([seed length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
valueForKey:NSStringFromMPElementType(type)];
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
valueForKey:NSStringFromMPElementType(type)];
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
// Encode the content, character by character, using subsequent seed bytes and the cipher.
assert([seed length] >= [cipher length] + 1);
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
for (NSUInteger c = 0; c < [cipher length]; ++c) {
uint16_t keyByte = htons(seedBytes[c + 1]);
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)];
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length],
1)];
trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
[content appendString:character];
}
return content;
}

View File

@@ -0,0 +1,32 @@
//
// MPUserEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPElementEntity;
@interface MPUserEntity : NSManagedObject
@property (nonatomic, retain) NSNumber * avatar_;
@property (nonatomic, retain) NSData * keyID;
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSNumber * defaultType_;
@property (nonatomic, retain) NSSet *elements;
@end
@interface MPUserEntity (CoreDataGeneratedAccessors)
- (void)addElementsObject:(MPElementEntity *)value;
- (void)removeElementsObject:(MPElementEntity *)value;
- (void)addElements:(NSSet *)values;
- (void)removeElements:(NSSet *)values;
@end

View File

@@ -0,0 +1,23 @@
//
// MPUserEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPUserEntity.h"
#import "MPElementEntity.h"
@implementation MPUserEntity
@dynamic avatar_;
@dynamic keyID;
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey_;
@dynamic defaultType_;
@dynamic elements;
@end

View File

@@ -10,16 +10,16 @@
#import "MPAppDelegate_Shared.h"
#import "MPPasswordWindowController.h"
@interface MPAppDelegate : MPAppDelegate_Shared <NSApplicationDelegate>
@interface MPAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
@property (strong) NSStatusItem *statusItem;
@property (strong) MPPasswordWindowController *passwordWindow;
@property (weak) IBOutlet NSMenuItem *lockItem;
@property (weak) IBOutlet NSMenuItem *showItem;
@property (strong) IBOutlet NSMenu *statusMenu;
@property (weak) IBOutlet NSMenuItem *useICloudItem;
@property (weak) IBOutlet NSMenuItem *rememberPasswordItem;
@property (weak) IBOutlet NSMenuItem *savePasswordItem;
@property (strong) NSStatusItem *statusItem;
@property (strong) MPPasswordWindowController *passwordWindow;
@property (weak) IBOutlet NSMenuItem *lockItem;
@property (weak) IBOutlet NSMenuItem *showItem;
@property (strong) IBOutlet NSMenu *statusMenu;
@property (weak) IBOutlet NSMenuItem *useICloudItem;
@property (weak) IBOutlet NSMenuItem *rememberPasswordItem;
@property (weak) IBOutlet NSMenuItem *savePasswordItem;
+ (MPAppDelegate *)get;

View File

@@ -27,50 +27,52 @@
@synthesize key;
@synthesize keyID;
#pragma GCC diagnostic ignored "-Wfour-char-constants"
static EventHotKeyID MPShowHotKey = { .signature = 'show', .id = 1 };
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfour-char-constants"
static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1};
#pragma clang diagnostic pop
+ (void)initialize {
[MPConfig get];
#ifdef DEBUG
[PearlLogger get].autoprintLevel = PearlLogLevelTrace;
#endif
}
+ (MPAppDelegate *)get {
return (MPAppDelegate *)[super get];
}
static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData){
static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) {
// Extract the hotkey ID.
EventHotKeyID hotKeyID;
GetEventParameter(theEvent,kEventParamDirectObject,typeEventHotKeyID,
NULL,sizeof(hotKeyID),NULL,&hotKeyID);
EventHotKeyID hotKeyID;
GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID,
NULL, sizeof(hotKeyID), NULL, &hotKeyID);
// Check which hotkey this was.
if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) {
[((__bridge MPAppDelegate *)userData) activate:nil];
return noErr;
}
return eventNotHandledErr;
}
- (void)showMenu {
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
self.showItem.enabled = ![self.passwordWindow.window isVisible];
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
self.showItem.enabled = ![self.passwordWindow.window isVisible];
[self.statusItem popUpStatusItemMenu:self.statusMenu];
}
- (IBAction)activate:(id)sender {
if ([[NSApplication sharedApplication] isActive])
[self applicationDidBecomeActive:nil];
else
@@ -78,13 +80,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
}
- (IBAction)togglePreference:(NSMenuItem *)sender {
if (sender == useICloudItem)
[self.storeManager useiCloudStore:sender.state == NSOffState alertUser:YES];
if (sender == rememberPasswordItem)
[MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]];
if (sender == savePasswordItem)
[MPConfig get].saveKey = [NSNumber numberWithBool:![[MPConfig get].saveKey boolValue]];
[MPConfig get].saveKey = [NSNumber numberWithBool:![[MPConfig get].saveKey boolValue]];
}
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
@@ -92,11 +94,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
if (configKey == @selector(rememberKey))
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
if (configKey == @selector(saveKey))
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"key"]) {
if (self.key)
[self.lockItem setEnabled:YES];
@@ -108,49 +110,50 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
}
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
return [[self managedObjectContext] undoManager];
}
#pragma mark - NSApplicationDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Setup delegates and listeners.
[MPConfig get].delegate = self;
[self addObserver:self forKeyPath:@"key" options:0 context:nil];
// Initially, use iCloud.
if ([[MPConfig get].firstRun boolValue])
[[self storeManager] useiCloudStore:YES alertUser:YES];
// Status item.
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
self.statusItem.title = @"•••";
self.statusItem.highlightMode = YES;
self.statusItem.target = self;
self.statusItem.action = @selector(showMenu);
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
self.statusItem.title = @"•••";
self.statusItem.highlightMode = YES;
self.statusItem.target = self;
self.statusItem.action = @selector(showMenu);
// Global hotkey.
EventHotKeyRef hotKeyRef;
EventTypeSpec hotKeyEvents[1] = { { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed } };
OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents), hotKeyEvents,
(__bridge void *)self, NULL);
if(status != noErr)
err(@"Error installing application event handler: %d", status);
status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef);
if(status != noErr)
err(@"Error registering hotkey: %d", status);
EventTypeSpec hotKeyEvents[1] = {{.eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed}};
OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents),
hotKeyEvents,
(__bridge void *)self, NULL);
if (status != noErr)
err(@"Error installing application event handler: %d", status);
status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef);
if (status != noErr)
err(@"Error registering hotkey: %d", status);
}
- (void)applicationWillBecomeActive:(NSNotification *)notification {
if (!self.passwordWindow)
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
static BOOL firstTime = YES;
if (firstTime)
firstTime = NO;
@@ -159,62 +162,61 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
}
- (void)applicationWillResignActive:(NSNotification *)notification {
if (![[MPConfig get].rememberKey boolValue])
self.key = nil;
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
// Save changes in the application's managed object context before the application terminates.
if (![self managedObjectContext]) {
return NSTerminateNow;
}
if (![[self managedObjectContext] commitEditing]) {
NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd));
return NSTerminateCancel;
}
if (![[self managedObjectContext] hasChanges]) {
return NSTerminateNow;
}
NSError *error = nil;
if (![[self managedObjectContext] save:&error]) {
// Customize this code block to include application-specific recovery steps.
BOOL result = [sender presentError:error];
if (result) {
return NSTerminateCancel;
}
NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message");
NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info");
NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message");
NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info");
NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title");
NSAlert *alert = [[NSAlert alloc] init];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:question];
[alert setInformativeText:info];
[alert addButtonWithTitle:quitButton];
[alert addButtonWithTitle:cancelButton];
NSInteger answer = [alert runModal];
if (answer == NSAlertAlternateReturn) {
return NSTerminateCancel;
}
}
return NSTerminateNow;
}
#pragma mark - UbiquityStoreManagerDelegate
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState;
self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState;
self.useICloudItem.enabled = !iCloudEnabled;
}

View File

@@ -8,8 +8,8 @@
#import <Cocoa/Cocoa.h>
@interface MPPasswordWindowController : NSWindowController <NSTextFieldDelegate> {
@interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate> {
NSString *_content;
}

View File

@@ -27,10 +27,10 @@
@synthesize tipField;
- (void)windowDidLoad {
[self setContent:@""];
[self.tipField setStringValue:@""];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil
usingBlock:^(NSNotification *note) {
[self unlock];
@@ -45,32 +45,32 @@
NSString *newSiteName = [self.siteField stringValue];
BOOL shouldComplete = [self.oldSiteName length] < [newSiteName length];
self.oldSiteName = newSiteName;
if ([self trySite])
shouldComplete = NO;
if (shouldComplete)
[[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil];
}];
[super windowDidLoad];
}
- (void)unlock {
if (![MPAppDelegate get].key)
// Try and load the key from the keychain.
// Try and load the key from the keychain.
[[MPAppDelegate get] loadStoredKey];
if (![MPAppDelegate get].key)
// Ask the user to set the key through his master password.
// Ask the user to set the key through his master password.
dispatch_async(dispatch_get_main_queue(), ^{
if ([MPAppDelegate get].key)
return;
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
informativeTextWithFormat:@"Your master password is required to unlock the application."];
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
informativeTextWithFormat:@"Your master password is required to unlock the application."];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)];
[alert setAccessoryView:passwordField];
[alert layout];
@@ -80,50 +80,52 @@
});
}
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
switch (returnCode) {
case NSAlertAlternateReturn:
// "Change" button.
if ([[NSAlert alertWithMessageText:@"Changing Master Password"
defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil
informativeTextWithFormat:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."] runModal] == 1)
informativeTextWithFormat:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."] runModal]
== 1)
[[MPAppDelegate get] forgetKey];
break;
case NSAlertOtherReturn:
// "Quit" button.
[[NSApplication sharedApplication] terminate:self];
return;
case NSAlertDefaultReturn:
// "Unlock" button.
[[MPAppDelegate get] tryMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]];
}
}
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words
forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
NSString *query = [[control stringValue] substringWithRange:charRange];
if (![query length] || ![MPAppDelegate get].keyID)
return nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND keyID == %@",
query, query, [MPAppDelegate get].keyID];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
query, query, [MPAppDelegate get].activeUser];
NSError *error = nil;
self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"Couldn't fetch elements: %@", error);
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
err(@"Couldn't fetch elements: %@", error);
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
if (self.siteResults)
for (MPElementEntity *element in self.siteResults)
[mutableResults addObject:element.name];
@@ -132,7 +134,7 @@
}
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
if (commandSelector == @selector(cancel:)) {
[self.window close];
return YES;
@@ -149,68 +151,68 @@
[self.tipField.animator setAlphaValue:0];
[NSAnimationContext endGrouping];
});
[[self findElement] use];
return YES;
} else
wrn(@"Couldn't copy password to pasteboard.");
wrn(@"Couldn't copy password to pasteboard.");
}
return NO;
}
- (void)controlTextDidEndEditing:(NSNotification *)obj {
if (obj.object == self.siteField)
[self trySite];
}
- (NSString *)content {
return _content;
}
- (void)setContent:(NSString *)content {
_content = content;
NSShadow *shadow = [NSShadow new];
shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f];
shadow.shadowOffset = NSMakeSize(1.0f, -1.0f);
shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f];
shadow.shadowOffset = NSMakeSize(1.0f, -1.0f);
shadow.shadowBlurRadius = 1.2f;
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
paragraph.alignment = NSCenterTextAlignment;
[self.contentField setAttributedStringValue:
[[NSAttributedString alloc] initWithString:_content
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
shadow, NSShadowAttributeName,
paragraph, NSParagraphStyleAttributeName,
nil]]];
[[NSAttributedString alloc] initWithString:_content
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
shadow, NSShadowAttributeName,
paragraph, NSParagraphStyleAttributeName,
nil]]];
}
- (BOOL)trySite {
MPElementEntity *result = [self findElement];
if (!result) {
[self setContent:@""];
[self.tipField setStringValue:@""];
return NO;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [result.content description];
if (!description)
description = @"";
dispatch_async(dispatch_get_main_queue(), ^{
[self setContent:description];
[self.tipField setStringValue:@"Hit enter to copy the password."];
self.tipField.alphaValue = 1;
});
});
// For when the app should be able to create new sites.
/*
else
@@ -231,16 +233,16 @@
});
}];
*/
return YES;
}
- (MPElementEntity *)findElement {
for (MPElementEntity *element in self.siteResults)
if ([element.name isEqualToString:[self.siteField stringValue]])
return element;
return nil;
}

View File

@@ -3,7 +3,9 @@
//
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#import <Cocoa/Cocoa.h>
#endif
#import "Pearl-Prefix.pch"

View File

@@ -8,7 +8,7 @@
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
int main(int argc, char *argv[]) {
return NSApplicationMain(argc, (const char **)argv);
}

View File

@@ -1,21 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<attribute name="keyID" attributeType="Binary" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="16" syncable="YES"/>
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
</entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="counter" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
</entity>
<elements>
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="120"/>
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>
</elements>
</model>

View File

@@ -10,13 +10,14 @@
#import <MessageUI/MessageUI.h>
#import "MPAppDelegate_Shared.h"
@interface MPAppDelegate : MPAppDelegate_Shared <MFMailComposeViewControllerDelegate>
@interface MPAppDelegate : MPAppDelegate_Shared<MFMailComposeViewControllerDelegate>
+ (MPAppDelegate *)get;
- (void)checkConfig;
- (void)showGuide;
- (void)loadKey:(BOOL)animated;
- (void)export;
- (void)changeMasterPasswordFor:(MPUserEntity *)user;
@end

View File

@@ -10,21 +10,18 @@
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "MPMainViewController.h"
#import "IASKSettingsReader.h"
#import "LocalyticsSession.h"
#import "TestFlight.h"
#import <Crashlytics/Crashlytics.h>
@interface MPAppDelegate ()
- (NSString *)testFlightInfo;
- (NSDictionary *)testFlightInfo;
- (NSString *)testFlightToken;
- (NSString *)crashlyticsInfo;
- (NSDictionary *)crashlyticsInfo;
- (NSString *)crashlyticsAPIKey;
- (NSString *)localyticsInfo;
- (NSDictionary *)localyticsInfo;
- (NSString *)localyticsKey;
@end
@@ -33,9 +30,9 @@
@implementation MPAppDelegate
+ (void)initialize {
[MPiOSConfig get];
#ifdef DEBUG
[PearlLogger get].autoprintLevel = PearlLogLevelDebug;
//[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
@@ -43,223 +40,155 @@
}
+ (MPAppDelegate *)get {
return (MPAppDelegate *)[super get];
}
- (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
}
- (void)loadKey:(BOOL)animated {
if (!self.key)
// Try and load the key from the keychain.
[self loadStoredKey];
if (!self.key)
// Ask the user to set the key through his master password.
if ([NSThread isMainThread])
[self.navigationController presentViewController:
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil];
else
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController presentViewController:
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil];
});
}
- (void)export {
[PearlAlert showNotice:
@"This will export all your site names.\n\n"
@"You can open the export with a text editor to get an overview of all your sites.\n\n"
@"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes."
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
@"Would you like to make all your passwords visible in the export?\n\n"
@"A safe export will only include your stored passwords, in an encrypted manner, "
@"making the result safe from falling in the wrong hands.\n\n"
@"If all your passwords are shown and somebody else finds the export, "
@"they could gain access to all your sites!"
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex] + 0)
// Safe Export
[self exportShowPasswords:NO];
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
// Safe Export
[self exportShowPasswords:YES];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
} otherTitles:nil];
}
- (void)exportShowPasswords:(BOOL)showPasswords {
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
NSString *message;
if (showPasswords)
message = @"Export of my Master Password sites with passwords visible.\n\nREMINDER: Make sure nobody else sees this file!\n";
else
message = @"Backup of my Master Password sites.\n";
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"'Master Password sites ('yyyy'-'MM'-'DD').mpsites'"];
MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init];
[composer setMailComposeDelegate:self];
[composer setSubject:@"Master Password site export"];
[composer setMessageBody:message isHTML:NO];
[composer addAttachmentData:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
fileName:[exportDateFormatter stringFromDate:[NSDate date]]];
[self.window.rootViewController presentModalViewController:composer animated:YES];
}
#pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
[self checkConfig];
}
- (void)checkConfig {
if ([[MPConfig get].saveKey boolValue]) {
if (self.key)
[self updateKey:self.key];
} else
[self loadStoredKey];
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
}
#pragma mark - UIApplicationDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#ifndef DEBUG
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
@try {
NSString *testFlightToken = [self testFlightToken];
if ([testFlightToken length]) {
dbg(@"Initializing TestFlight");
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], @"logToConsole",
[NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight takeOff:testFlightToken];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelInfo)
TFLog(@"%@", message);
return YES;
}];
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
}
}
@catch (NSException *exception) {
err(@"TestFlight: %@", exception);
}
@try {
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
if ([crashlyticsAPIKey length]) {
dbg(@"Initializing Crashlytics");
//[Crashlytics sharedInstance].debugMode = YES;
[Crashlytics startWithAPIKey:crashlyticsAPIKey afterDelay:0];
}
}
@catch (NSException *exception) {
err(@"Crashlytics: %@", exception);
}
@try {
NSString *localyticsKey = [self localyticsKey];
if ([localyticsKey length]) {
dbg(@"Initializing Localytics");
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelError)
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[message levelDescription],
@"level",
message.message,
@"message",
nil]];
return YES;
}];
}
}
@catch (NSException *exception) {
err(@"Localytics exception: %@", exception);
}
});
[[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
@try {
NSString *testFlightToken = [self testFlightToken];
if ([testFlightToken length]) {
inf(@"Initializing TestFlight");
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
#ifdef ADHOC
[TestFlight setDeviceIdentifier:[(id)[UIDevice currentDevice] uniqueIdentifier]];
#else
[TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
#endif
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], @"logToConsole",
[NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight takeOff:testFlightToken];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPiOSConfig get].sendInfo boolValue])
level = PearlLogLevelInfo;
if (message.level >= level)
TFLog(@"%@", message);
return YES;
}];
}
}
@catch (id exception) {
err(@"TestFlight: %@", exception);
}
@try {
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
if ([crashlyticsAPIKey length]) {
inf(@"Initializing Crashlytics");
#if defined (DEBUG) || defined (ADHOC)
[Crashlytics sharedInstance].debugMode = YES;
#endif
[[Crashlytics sharedInstance] setObjectValue:@"Anonymous" forKey:@"username"];
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
[Crashlytics startWithAPIKey:crashlyticsAPIKey afterDelay:0];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPiOSConfig get].sendInfo boolValue])
level = PearlLogLevelInfo;
if (message.level >= level)
CLSLog(@"%@", message);
return YES;
}];
}
}
@catch (id exception) {
err(@"Crashlytics: %@", exception);
}
@try {
NSString *localyticsKey = [self localyticsKey];
if ([localyticsKey length]) {
inf(@"Initializing Localytics");
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelWarn)
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[message levelDescription],
@"level",
message.message,
@"message",
nil]];
return YES;
}];
}
}
@catch (id exception) {
err(@"Localytics exception: %@", exception);
}
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone];
[[UINavigationBar appearance] setTitleTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont,
nil]];
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont,
nil]];
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
[[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setTitleTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
nil]
forState:UIControlStateNormal];
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
nil]
forState:UIControlStateNormal];
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
/*
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
*/
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
usingBlock:^(NSNotification *note) {
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
usingBlock:^(NSNotification *note) {
[self checkConfig];
}];
#ifdef ADHOC
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
@"Thank you for taking the time to test Master Password.\n\n"
@@ -268,58 +197,66 @@
@"lhunath@lyndir.com\n"
@"Or report detailed issues at:\n"
@"https://youtrack.lyndir.com\n"
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#endif
[[UIApplication sharedApplication] setStatusBarHidden:NO
withAnimation:UIStatusBarAnimationSlide];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
[super application:application didFinishLaunchingWithOptions:launchOptions];
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
[TestFlight passCheckpoint:MPCheckpointLaunched];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLaunched];
return YES;
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
__autoreleasing NSError *error;
__autoreleasing NSError *error;
__autoreleasing NSURLResponse *response;
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error];
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error];
if (error)
err(@"While reading imported sites from %@: %@", url, error);
err(@"While reading imported sites from %@: %@", url, error);
if (!importedSitesData)
return NO;
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
[PearlAlert showAlertWithTitle:@"Import Password" message:
@"Enter the master password for this export:"
viewStyle:UIAlertViewStyleSecureTextInput tappedButtonBlock:
@"Enter the master password for this export:"
viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
__block BOOL confirmation = NO;
dispatch_group_t confirmationGroup = dispatch_group_create();
dispatch_group_enter(confirmationGroup);
dispatch_async(dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import Sites?"
message:PearlLocalize(@"Import %d sites, overwriting %d existing sites?", importCount, deleteCount)
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex != [alert cancelButtonIndex])
confirmation = YES;
dispatch_group_leave(confirmationGroup);
}
cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:@"Import", nil];
});
dispatch_group_wait(confirmationGroup, DISPATCH_TIME_FOREVER);
return confirmation;
}];
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
__block BOOL confirmation = NO;
dispatch_group_t confirmationGroup = dispatch_group_create();
dispatch_group_enter(confirmationGroup);
dispatch_async(dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import Sites?"
message:PearlString(
@"Import %d sites, overwriting %d existing sites?",
importCount, deleteCount)
viewStyle:UIAlertViewStyleDefault
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_
!= [alert_ cancelButtonIndex])
confirmation = YES;
dispatch_group_leave(confirmationGroup);
}
cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:@"Import", nil];
});
dispatch_group_wait(
confirmationGroup, DISPATCH_TIME_FOREVER);
return confirmation;
}];
switch (result) {
case MPImportResultSuccess:
case MPImportResultCancelled:
@@ -336,130 +273,306 @@
}
});
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
return YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
inf(@"Re-activated");
[[MPAppDelegate get] checkConfig];
if ([[MPiOSConfig get].showQuickStart boolValue])
[self showGuide];
else {
[self loadKey:NO];
[self checkConfig];
}
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
[TestFlight passCheckpoint:MPCheckpointActivated];
[super applicationDidBecomeActive:application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[LocalyticsSession sharedLocalyticsSession] resume];
[[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationWillEnterForeground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
[self saveContext];
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
[TestFlight passCheckpoint:MPCheckpointTerminated];
[[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationWillTerminate:application];
}
- (void)applicationWillResignActive:(UIApplication *)application {
inf(@"Will deactivate");
[self saveContext];
if (![[MPiOSConfig get].rememberKey boolValue]) {
[self updateKey:nil];
[self loadKey:NO];
if (![[MPiOSConfig get].rememberLogin boolValue])
[self signOut];
[TestFlight passCheckpoint:MPCheckpointDeactivated];
}
#pragma mark - Behavior
- (void)checkConfig {
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
if ([[MPiOSConfig get].sendInfo boolValue]) {
if ([PearlLogger get].autoprintLevel > PearlLogLevelInfo)
[PearlLogger get].autoprintLevel = PearlLogLevelInfo;
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloud boolValue] forKey:@"iCloud"];
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloudDecided boolValue] forKey:@"iCloudDecided"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].sendInfo boolValue] forKey:@"sendInfo"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].helpHidden boolValue] forKey:@"helpHidden"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].showQuickStart boolValue] forKey:@"showQuickStart"];
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].firstRun boolValue] forKey:@"firstRun"];
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].launchCount intValue] forKey:@"launchCount"];
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].askForReviews boolValue] forKey:@"askForReviews"];
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].reviewAfterLaunches intValue] forKey:@"reviewAfterLaunches"];
[[Crashlytics sharedInstance] setObjectValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].rememberLogin boolValue]? @"YES": @"NO" forKey:@"rememberLogin"];
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloud boolValue]? @"YES": @"NO" forKey:@"iCloud"];
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloudDecided boolValue]? @"YES": @"NO" forKey:@"iCloudDecided"];
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].sendInfo boolValue]? @"YES": @"NO" forKey:@"sendInfo"];
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].helpHidden boolValue]? @"YES": @"NO" forKey:@"helpHidden"];
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].showQuickStart boolValue]? @"YES": @"NO" forKey:@"showQuickStart"];
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].firstRun boolValue]? @"YES": @"NO" forKey:@"firstRun"];
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].launchCount description] forKey:@"launchCount"];
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].askForReviews boolValue]? @"YES": @"NO" forKey:@"askForReviews"];
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].reviewAfterLaunches description] forKey:@"reviewAfterLaunches"];
[TestFlight addCustomEnvironmentInformation:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
[TestFlight passCheckpoint:MPCheckpointConfig];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointConfig attributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[[MPConfig get].rememberLogin boolValue]
? @"YES": @"NO", @"rememberLogin",
[[MPConfig get].iCloud boolValue]? @"YES"
: @"NO", @"iCloud",
[[MPConfig get].iCloudDecided boolValue]
? @"YES": @"NO", @"iCloudDecided",
[[MPiOSConfig get].sendInfo boolValue]
? @"YES": @"NO", @"sendInfo",
[[MPiOSConfig get].helpHidden boolValue]
? @"YES": @"NO", @"helpHidden",
[[MPiOSConfig get].showQuickStart boolValue]
? @"YES": @"NO", @"showQuickStart",
[[PearlConfig get].firstRun boolValue]
? @"YES": @"NO", @"firstRun",
[[PearlConfig get].launchCount description], @"launchCount",
[[PearlConfig get].askForReviews boolValue]
? @"YES": @"NO", @"askForReviews",
[[PearlConfig get].reviewAfterLaunches description], @"reviewAfterLaunches",
[PearlConfig get].reviewedVersion, @"reviewedVersion",
nil]];
}
}
- (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
[TestFlight passCheckpoint:MPCheckpointShowGuide];
}
- (void)export {
[PearlAlert showNotice:
@"This will export all your site names.\n\n"
@"You can open the export with a text editor to get an overview of all your sites.\n\n"
@"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes."
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
@"Would you like to make all your passwords visible in the export?\n\n"
@"A safe export will only include your stored passwords, in an encrypted manner, "
@"making the result safe from falling in the wrong hands.\n\n"
@"If all your passwords are shown and somebody else finds the export, "
@"they could gain access to all your sites!"
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0)
// Safe Export
[self exportShowPasswords:NO];
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
// Show Passwords
[self exportShowPasswords:YES];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
} otherTitles:nil];
}
- (void)exportShowPasswords:(BOOL)showPasswords {
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
if (![MFMailComposeViewController canSendMail]) {
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
message:
@"Your device is not yet set up for sending mail.\n"
@"Close Master Password, go into Settings and add a Mail account."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:nil
cancelTitle:[PearlStrings get].commonButtonOkay
otherTitles:nil];
return;
}
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
NSString *message;
if (showPasswords)
message = PearlString(@"Export of Master Password sites with passwords included.\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
self.activeUser.name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion);
else
message = PearlString(@"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
self.activeUser.name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion);
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"];
MFMailComposeViewController *composer = [MFMailComposeViewController new];
[composer setMailComposeDelegate:self];
[composer setSubject:@"Master Password Export"];
[composer setMessageBody:message isHTML:NO];
[composer addAttachmentData:
[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
fileName:PearlString(@"%@ (%@).mpsites",
self.activeUser.name,
[exportDateFormatter stringFromDate:[NSDate date]])];
[self.window.rootViewController presentModalViewController:composer animated:YES];
}
- (void)changeMasterPasswordFor:(MPUserEntity *)user {
[PearlAlert showAlertWithTitle:@"Changing Master Password"
message:
@"If you continue, you'll be able to set a new master password.\n\n"
@"Changing your master password will cause all your generated passwords to change!\n"
@"Changing the master password back to the old one will cause your passwords to revert as well."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
inf(@"Unsetting master password for: %@.", user.userID);
user.keyID = nil;
[self forgetSavedKeyFor:user];
[self signOut];
[TestFlight passCheckpoint:MPCheckpointChangeMP];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP
attributes:nil];
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
#pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
[self checkConfig];
}
#pragma mark - MFMailComposeViewControllerDelegate
- (void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
if (error)
err(@"Error composing mail message: %@", error);
err(@"Error composing mail message: %@", error);
switch (result) {
case MFMailComposeResultSaved:
case MFMailComposeResultSent:
break;
case MFMailComposeResultFailed:
[PearlAlert showError:@"A problem occurred while sending the message." tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex])
return;
} otherTitles:@"Retry", nil];
[PearlAlert showError:@"A problem occurred while sending the message."
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex])
return;
} otherTitles:@"Retry", nil];
return;
case MFMailComposeResultCancelled:
break;
}
[controller dismissModalViewControllerAnimated:YES];
}
#pragma mark - UbiquityStoreManagerDelegate
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
[super ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
if (![[MPConfig get].iCloudDecided boolValue]) {
if (!iCloudEnabled) {
[PearlAlert showAlertWithTitle:@"iCloud"
message:
@"iCloud is now disabled.\n\n"
@"It is highly recommended you enable iCloud."
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex] + 0) {
[PearlAlert showAlertWithTitle:@"About iCloud"
message:
@"iCloud is Apple's solution for saving your data in \"the cloud\" "
@"and making sure your other iPhones, iPads and Macs are in sync.\n\n"
@"For Master Password, that means your sites are available on all your "
@"Apple devices, and you always have a backup of them in case "
@"you loose one or need to restore.\n\n"
@"Because of the way Master Password works, it doesn't need to send your "
@"site's passwords to Apple. Only their names are saved to make it easier "
@"for you to find the site you need. For some sites you may have set "
@"a user-specified password: these are sent to iCloud after being encrypted "
@"with your master password.\n\n"
@"Apple can never see any of your passwords."
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
}
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
return;
}
@"iCloud is now disabled.\n\n"
@"It is highly recommended you enable iCloud."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex] + 0) {
[PearlAlert showAlertWithTitle:@"About iCloud"
message:
@"iCloud is Apple's solution for saving your data in \"the cloud\" "
@"and making sure your other iPhones, iPads and Macs are in sync.\n\n"
@"For Master Password, that means your sites are available on all your "
@"Apple devices, and you always have a backup of them in case "
@"you loose one or need to restore.\n\n"
@"Because of the way Master Password works, it doesn't need to send your "
@"site's passwords to Apple. Only their names are saved to make it easier "
@"for you to find the site you need. For some sites you may have set "
@"a user-specified password: these are sent to iCloud after being encrypted "
@"with your master password.\n\n"
@"Apple can never see any of your passwords."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
}
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
return;
}
[MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES];
if (buttonIndex == [alert cancelButtonIndex])
return;
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
[manager useiCloudStore:YES alertUser:NO];
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
[MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES];
if (buttonIndex == [alert cancelButtonIndex])
return;
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
[manager useiCloudStore:YES alertUser:NO];
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
}
}
}
@@ -468,18 +581,18 @@
- (NSDictionary *)testFlightInfo {
static NSDictionary *testFlightInfo = nil;
if (testFlightInfo == nil)
testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
return testFlightInfo;
}
- (NSString *)testFlightToken {
return NullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
}
@@ -487,18 +600,18 @@
- (NSDictionary *)crashlyticsInfo {
static NSDictionary *crashlyticsInfo = nil;
if (crashlyticsInfo == nil)
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
return crashlyticsInfo;
}
- (NSString *)crashlyticsAPIKey {
return NullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
}
@@ -506,23 +619,21 @@
- (NSDictionary *)localyticsInfo {
static NSDictionary *localyticsInfo = nil;
if (localyticsInfo == nil)
localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
return localyticsInfo;
}
- (NSString *)localyticsKey {
#ifdef DEBUG
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
#elif defined(LITE)
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution.lite"]);
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
#else
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
#endif
}

View File

@@ -8,9 +8,10 @@
#import <UIKit/UIKit.h>
@interface MPGuideViewController : UIViewController
@interface MPGuideViewController : UIViewController <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
- (IBAction)close;

View File

@@ -7,42 +7,49 @@
//
#import "MPGuideViewController.h"
#import "MPAppDelegate.h"
@implementation MPGuideViewController
@synthesize scrollView;
@synthesize pageControl;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)viewDidLoad {
[super viewDidLoad];
[PearlUIUtils autoSizeContent:self.scrollView];
[self.scrollView autoSizeContent];
}
- (void)viewWillAppear:(BOOL)animated {
inf(@"Guide will appear.");
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Guide will disappear.");
[super viewWillDisappear:animated];
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[MPAppDelegate get] loadKey:animated];
}
- (void)viewDidUnload {
[self setScrollView:nil];
[self setPageControl:nil];
[super viewDidUnload];
}
@@ -51,4 +58,12 @@
[self.presentingViewController dismissModalViewControllerAnimated:YES];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView_ {
NSInteger page = (NSInteger)(self.scrollView.contentOffset.x / self.scrollView.bounds.size.width);
self.pageControl.currentPage = page;
self.pageControl.hidden = (page == self.pageControl.numberOfPages - 1);
}
@end

View File

@@ -6,31 +6,34 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <MessageUI/MessageUI.h>
#import "MPTypeViewController.h"
#import "MPElementEntity.h"
#import "MPSearchDelegate.h"
#import "IASKAppSettingsViewController.h"
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate>
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, MFMailComposeViewControllerDelegate>
@property (strong, nonatomic) MPElementEntity *activeElement;
@property (strong, nonatomic) MPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
@property (weak, nonatomic) IBOutlet UITextField *contentField;
@property (weak, nonatomic) IBOutlet UIButton *typeButton;
@property (weak, nonatomic) IBOutlet UIWebView *helpView;
@property (weak, nonatomic) IBOutlet UILabel *siteName;
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
@property (weak, nonatomic) IBOutlet UIView *alertContainer;
@property (weak, nonatomic) IBOutlet UILabel *alertTitle;
@property (weak, nonatomic) IBOutlet UITextView *alertBody;
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@property (weak, nonatomic) IBOutlet UITextField *contentField;
@property (weak, nonatomic) IBOutlet UIButton *typeButton;
@property (weak, nonatomic) IBOutlet UIWebView *helpView;
@property (weak, nonatomic) IBOutlet UILabel *siteName;
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
@property (weak, nonatomic) IBOutlet UIView *alertContainer;
@property (weak, nonatomic) IBOutlet UILabel *alertTitle;
@property (weak, nonatomic) IBOutlet UITextView *alertBody;
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer;
@property (weak, nonatomic) IBOutlet UIView *typeTipContainer;
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *resetPasswordCounterGesture;
@property (copy) void (^contentTipCleanup)(BOOL finished);

View File

@@ -10,17 +10,12 @@
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h"
#import "IASKAppSettingsViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import "LocalyticsSession.h"
@interface MPMainViewController (Private)
- (void)updateAnimated:(BOOL)animated;
- (void)updateWasAnimated:(BOOL)animated;
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task;
@@ -45,84 +40,100 @@
@synthesize alertBody = _alertBody;
@synthesize contentTipBody = _contentTipBody;
@synthesize contentTipEditIcon = _contentTipEditIcon;
@synthesize searchTipContainer = _searchTip;
@synthesize searchTipContainer = _searchTipContainer;
@synthesize actionsTipContainer = _actionsTipContainer;
@synthesize typeTipContainer = _typeTipContainer;
@synthesize resetPasswordCounterGesture = _resetPasswordCounterGesture;
@synthesize contentField = _contentField;
@synthesize contentTipCleanup;
#pragma mark - View lifecycle
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad || interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self setHelpHidden:![self isHelpVisible] animated:NO];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:@"MP_Main_ChooseType"])
if ([[segue identifier] isEqualToString:@"MP_ChooseType"])
((MPTypeViewController *)[segue destinationViewController]).delegate = self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.searchTipContainer.hidden = NO;
if (![self.activeElement.keyID isEqualToData:[MPAppDelegate get].keyID])
self.activeElement = nil;
self.searchDisplayController.searchBar.text = nil;
if (!self.activeElement.name)
[UIView animateWithDuration:animated? 0.2f: 0 animations:^{
self.searchTipContainer.alpha = 1;
}];
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
[self updateAnimated:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.searchTipContainer.hidden = YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
- (void)viewDidLoad {
self.resetPasswordCounterGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(resetPasswordCounter:)];
[self.passwordIncrementer addGestureRecognizer:self.resetPasswordCounterGesture];
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize];
// Put the search tip on the window so it's above the nav bar.
if (![self.searchTipContainer.superview isEqual:self.navigationController.navigationBar.superview]) {
CGRect frameInWindow = [self.searchTipContainer.window convertRect:self.searchTipContainer.frame
fromView:self.searchTipContainer.superview];
[self.searchTipContainer removeFromSuperview];
[self.navigationController.navigationBar.superview addSubview:self.searchTipContainer];
self.searchTipContainer.frame = [self.searchTipContainer.window convertRect:frameInWindow
toView:self.searchTipContainer.superview];
}
self.alertBody.text = nil;
self.alertBody.text = nil;
self.contentTipEditIcon.hidden = YES;
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
inf(@"Main will appear.");
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
if (![MPAppDelegate get].activeUser)
[self.navigationController presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil];
if (self.activeElement.user != [MPAppDelegate get].activeUser)
self.activeElement = nil;
self.searchDisplayController.searchBar.text = nil;
self.searchTipContainer.alpha = 0;
self.actionsTipContainer.alpha = 0;
self.typeTipContainer.alpha = 0;
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
[self updateAnimated:animated];
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
if ([[MPiOSConfig get].firstRun boolValue])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.actionsTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{
self.actionsTipContainer.alpha = 0;
} completion:^(BOOL finished_) {
if (![self.activeElement.name length])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.searchTipContainer.alpha = 1;
}];
}];
});
}
}];
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Main will disappear.");
[super viewWillDisappear:animated];
}
- (void)viewDidUnload {
[self setContentField:nil];
[self setTypeButton:nil];
[self setSearchResultsController:nil];
@@ -140,46 +151,42 @@
[self setContentTipBody:nil];
[self setContentTipEditIcon:nil];
[self setSearchTipContainer:nil];
[self setActionsTipContainer:nil];
[self setTypeTipContainer:nil];
[self setResetPasswordCounterGesture:nil];
[super viewDidUnload];
}
- (void)updateAnimated:(BOOL)animated {
[[MPAppDelegate get] saveContext];
dispatch_async(dispatch_get_main_queue(), ^{
if (animated)
[UIView animateWithDuration:0.3f animations:^{
[self updateWasAnimated:animated];
}];
else
[self updateWasAnimated:animated];
});
}
- (void)updateWasAnimated:(BOOL)animated {
if (animated) {
[UIView animateWithDuration:0.3f animations:^{
[self updateAnimated:NO];
}];
return;
}
[self setHelpChapter:self.activeElement? @"2": @"1"];
self.siteName.text = self.activeElement.name;
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
[self.typeButton setTitle:NSStringFromMPElementType((unsigned)self.activeElement.type)
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
forState:UIControlStateNormal];
self.typeButton.alpha = NSStringFromMPElementType((unsigned)self.activeElement.type).length? 1: 0;
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
self.contentField.enabled = NO;
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
self.passwordCounter.text = [NSString stringWithFormat:@"%u", ((MPElementGeneratedEntity *) self.activeElement).counter];
self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter);
self.contentField.text = @"";
if (self.activeElement.name && ![self.activeElement isDeleted])
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [self.activeElement.content description];
dispatch_async(dispatch_get_main_queue(), ^{
self.contentField.text = description;
});
@@ -187,26 +194,26 @@
}
- (BOOL)isHelpVisible {
return self.helpContainer.frame.origin.y == 216;
}
- (void)toggleHelpAnimated:(BOOL)animated {
[self setHelpHidden:[self isHelpVisible] animated:animated];
}
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
if (hidden) {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES];
} else {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO];
}
}];
@@ -214,47 +221,46 @@
}
- (void)setHelpChapter:(NSString *)chapter {
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]];
[TestFlight passCheckpoint:PearlString(MPCheckpointHelpChapter @"_%@", chapter)];
dispatch_async(dispatch_get_main_queue(), ^{
[self.helpView loadRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:[NSString stringWithFormat:@"#%@", chapter] relativeToURL:
[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]];
NSURL *url = [NSURL URLWithString:[@"#" stringByAppendingString:chapter]
relativeToURL:[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]];
[self.helpView loadRequest:[NSURLRequest requestWithURL:url]];
});
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
ClassNameFromMPElementType((unsigned)self.activeElement.type)]];
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
if (error.length)
err(@"helpView.setClass: %@", error);
err(@"helpView.setClass: %@", error);
}
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.contentTipCleanup)
self.contentTipCleanup(NO);
self.contentTipBody.text = message;
self.contentTipCleanup = ^(BOOL finished) {
icon.hidden = YES;
self.contentTipCleanup = ^(BOOL finished) {
icon.hidden = YES;
self.contentTipCleanup = nil;
};
icon.hidden = NO;
[UIView animateWithDuration:0.2f animations:^{
[UIView animateWithDuration:0.3f animations:^{
self.contentTipContainer.alpha = 1;
} completion:^(BOOL finished) {
} completion:^(BOOL finished) {
if (finished) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[UIView animateWithDuration:0.2f animations:^{
self.contentTipContainer.alpha = 0;
} completion:self.contentTipCleanup];
} completion:self.contentTipCleanup];
});
}
}];
@@ -262,7 +268,7 @@
}
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
dispatch_async(dispatch_get_main_queue(), ^{
self.alertTitle.text = title;
NSRange scrollRange = NSMakeRange(self.alertBody.text.length, message.length);
@@ -271,8 +277,8 @@
else
self.alertBody.text = message;
[self.alertBody scrollRangeToVisible:scrollRange];
[UIView animateWithDuration:0.2f animations:^{
[UIView animateWithDuration:0.3f animations:^{
self.alertContainer.alpha = 1;
}];
});
@@ -281,268 +287,361 @@
#pragma mark - Protocols
- (IBAction)copyContent {
if (!self.activeElement)
return;
[[UIPasteboard generalPasteboard] setValue:self.activeElement.content
forPasteboardType:(id)kUTTypeUTF8PlainText];
inf(@"Copying password for: %@", self.activeElement.name);
[UIPasteboard generalPasteboard].string = [self.activeElement.content description];
[self showContentTip:@"Copied!" withIcon:nil];
[TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard];
[TestFlight passCheckpoint:MPCheckpointCopyToPasteboard];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
}
- (IBAction)incrementPasswordCounter {
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
// Not of a type that supports a password counter.
// Not of a type that supports a password counter.
return;
[self changeElementWithWarning:
@"You are incrementing the site's password counter.\n\n"
@"If you continue, a new password will be generated for this site. "
@"You will then need to update your account's old password to this newly generated password.\n\n"
@"You can reset the counter by holding down on this button."
@"You are incrementing the site's password counter.\n\n"
@"If you continue, a new password will be generated for this site. "
@"You will then need to update your account's old password to this newly generated password.\n\n"
@"You can reset the counter by holding down on this button."
do:^{
++((MPElementGeneratedEntity *) self.activeElement).counter;
inf(@"Incrementing password counter for: %@", self.activeElement.name);
++((MPElementGeneratedEntity *)self.activeElement).counter;
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
}];
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter];
}
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan)
// Only fire when the gesture was first detected.
// Only fire when the gesture was first detected.
return;
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
// Not of a type that supports a password counter.
// Not of a type that supports a password counter.
return;
if (((MPElementGeneratedEntity *)self.activeElement).counter == 1)
// Counter has initial value, no point resetting.
// Counter has initial value, no point resetting.
return;
[self changeElementWithWarning:
@"You are resetting the site's password counter.\n\n"
@"If you continue, the site's password will change back to its original value. "
@"You will then need to update your account's password back to this original value."
@"You are resetting the site's password counter.\n\n"
@"If you continue, the site's password will change back to its original value. "
@"You will then need to update your account's password back to this original value."
do:^{
((MPElementGeneratedEntity *) self.activeElement).counter = 1;
inf(@"Resetting password counter for: %@", self.activeElement.name);
((MPElementGeneratedEntity *)self.activeElement).counter = 1;
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
}];
[TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter];
}
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[self changeElementWithoutWarningDo:task];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[self changeElementWithoutWarningDo:task];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
- (void)changeElementWithoutWarningDo:(void (^)(void))task; {
// Update element, keeping track of the old password.
NSString *oldPassword = [self.activeElement.content description];
task();
NSString *newPassword = [self.activeElement.content description];
[[MPAppDelegate get] saveContext];
[self updateAnimated:YES];
// Show new and old password.
if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
[self showAlertWithTitle:@"Password Changed!" message:PearlLocalize(@"The password for %@ has changed.\n\n"
@"IMPORTANT:\n"
@"Don't forget to update the site with your new password! "
@"Your old password was:\n"
@"%@", self.activeElement.name, oldPassword)];
[self showAlertWithTitle:@"Password Changed!"
message:PearlString(@"The password for %@ has changed.\n\n"
@"IMPORTANT:\n"
@"Don't forget to update the site with your new password! "
@"Your old password was:\n"
@"%@", self.activeElement.name, oldPassword)];
}
- (IBAction)editPassword {
if (self.activeElement.type & MPElementTypeClassStored) {
self.contentField.enabled = YES;
[self.contentField becomeFirstResponder];
[TestFlight passCheckpoint:MPCheckpointEditPassword];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
nil]];
}
[TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword];
}
- (IBAction)closeAlert {
[UIView animateWithDuration:0.3f animations:^{
self.alertContainer.alpha = 0;
} completion:^(BOOL finished) {
} completion:^(BOOL finished) {
if (finished)
self.alertBody.text = nil;
}];
[TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert];
[TestFlight passCheckpoint:MPCheckpointCloseAlert];
}
- (IBAction)action:(id)sender {
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
switch (buttonIndex - [sheet firstOtherButtonIndex]) {
case 0:
case 0: {
inf(@"Action: Toggle Help");
[self toggleHelpAnimated:YES];
break;
}
case 1: {
inf(@"Action: FAQ");
[self setHelpChapter:@"faq"];
[self setHelpHidden:NO animated:YES];
break;
}
case 2:
case 2: {
inf(@"Action: Guide");
[[MPAppDelegate get] showGuide];
break;
case 3:
{
IASKAppSettingsViewController *settingsVC = [IASKAppSettingsViewController new];
settingsVC.delegate = self;
[self.navigationController pushViewController:settingsVC animated:YES];
}
case 3: {
inf(@"Action: Preferences");
[self performSegueWithIdentifier:@"UserProfile" sender:self];
break;
}
case 4:
[[MPAppDelegate get] export];
break;
#ifdef ADHOC
case 5:
case 4: {
inf(@"Action: Feedback via TestFlight");
[TestFlight openFeedbackView];
break;
case 6:
}
case 5:
#else
case 4: {
inf(@"Action: Feedback via Mail");
if (![MFMailComposeViewController canSendMail])
[PearlAlert showAlertWithTitle:@"Sending Feedback"
message:
@"We'd love to hear what you think!\n\n"
@"Please send any comments or reports to:\n"
@"masterpassword@lyndir.com"
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
otherTitles:nil];
else {
[PearlAlert showAlertWithTitle:@"Sending Feedback"
message:
@"We'd love to hear what you think!\n\n"
@"If you're having trouble, it may help us if you can first reproduce the problem "
@"and then include log files in your message."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
MFMailComposeViewController *composer = [MFMailComposeViewController new];
[composer setMailComposeDelegate:self];
[composer setToRecipients:[NSArray arrayWithObject:@"Master Password Development <masterpassword@lyndir.com>"]];
[composer setSubject:PearlString(@"Feedback for Master Password [%@]", [[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
[composer setMessageBody:
PearlString(
@"\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[MPAppDelegate get].activeUser.name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion)
isHTML:NO];
if (buttonIndex_ == [alert_ firstOtherButtonIndex]) {
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
[composer addAttachmentData:[[[PearlLogger get] formatMessagesWithLevel:logLevel] dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain"
fileName:PearlString(@"%@-%@.log",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
[PearlKeyChain deviceIdentifier])];
}
[self presentModalViewController:composer animated:YES];
}
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
}
break;
}
case 5:
#endif
{
[[MPAppDelegate get] signOut:self];
[[MPAppDelegate get] loadKey:YES];
inf(@"Action: Sign out");
[[MPAppDelegate get] signOut];
break;
}
}
[TestFlight passCheckpoint:MPTestFlightCheckpointAction];
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil
otherTitles:
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings", @"Export",
#ifdef ADHOC
@"Feedback",
#endif
@"Sign Out",
nil];
[TestFlight passCheckpoint:MPCheckpointAction];
}
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error {
if (error)
err(@"Feedback composer error: %@, result: %d", error, result);
else
inf(@"Feedback composer result: %d", result);
[controller dismissViewControllerAnimated:YES completion:nil];
}
- (MPElementType)selectedType {
return (unsigned)self.activeElement.type;
return self.activeElement.type;
}
- (void)didSelectType:(MPElementType)type {
[self changeElementWithWarning:
@"You are about to change the type of this password.\n\n"
@"If you continue, the password for this site will change. "
@"You will need to update your account's old password to the new one."
@"You are about to change the type of this password.\n\n"
@"If you continue, the password for this site will change. "
@"You will need to update your account's old password to the new one."
do:^{
// Update password type.
if (ClassFromMPElementType(type) != ClassFromMPElementType((unsigned)self.activeElement.type))
// Type requires a different class of element. Recreate the element.
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
// Type requires a different class of element. Recreate the element.
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(
type)
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name;
newElement.keyID = self.activeElement.keyID;
newElement.uses = self.activeElement.uses;
newElement.name = self.activeElement.name;
newElement.user = self.activeElement.user;
newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed;
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
self.activeElement = newElement;
}];
self.activeElement.type = type;
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}];
}
- (void)didSelectElement:(MPElementEntity *)element {
inf(@"Selected: %@", element.name);
[self closeAlert];
if (element) {
self.activeElement = element;
if ([self.activeElement use] == 1)
[self showAlertWithTitle:@"New Site" message:
PearlLocalize(@"You've just created a password for %@.\n\n"
@"IMPORTANT:\n"
@"Go to %@ and set or change the password for your account to the password above.\n"
@"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.",
self.activeElement.name, self.activeElement.name)];
PearlString(@"You've just created a password for %@.\n\n"
@"IMPORTANT:\n"
@"Go to %@ and set or change the password for your account to the password above.\n"
@"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.",
self.activeElement.name, self.activeElement.name)];
[[MPAppDelegate get] saveContext];
if ([[MPiOSConfig get].firstRun boolValue])
[UIView animateWithDuration:0.5f animations:^{
self.typeTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{
self.typeTipContainer.alpha = 0;
}];
});
}
}];
[self.searchDisplayController setActive:NO animated:YES];
self.searchDisplayController.searchBar.text = self.activeElement.name;
[TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement];
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", NSStringFromMPElementType(self.activeElement.type))];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
nil]];
}
[self updateAnimated:YES];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == self.contentField)
[self.contentField resignFirstResponder];
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if (textField == self.contentField) {
self.contentField.enabled = NO;
if (![self.activeElement isKindOfClass:[MPElementStoredEntity class]])
// Not of a type whose content can be edited.
// Not of a type whose content can be edited.
return;
if ([((MPElementStoredEntity *) self.activeElement).content isEqual:self.contentField.text])
// Content hasn't changed.
if ([((MPElementStoredEntity *)self.activeElement).content isEqual:self.contentField.text])
// Content hasn't changed.
return;
[self changeElementWithoutWarningDo:^{
((MPElementStoredEntity *) self.activeElement).content = self.contentField.text;
((MPElementStoredEntity *)self.activeElement).content = self.contentField.text;
}];
}
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
[TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink];
inf(@"External link: %@", [request URL]);
[TestFlight passCheckpoint:MPCheckpointExternalLink];
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
return YES;
}
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
while ([self.navigationController.viewControllers containsObject:sender])
[self.navigationController popViewControllerAnimated:YES];
}
@end

View File

@@ -0,0 +1,25 @@
//
// MPPreferencesViewController.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "IASKAppSettingsViewController.h"
#import "MPTypeViewController.h"
@interface MPPreferencesViewController : UITableViewController<IASKSettingsDelegate, MPTypeDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView;
@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate;
@property (weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch;
@property (weak, nonatomic) IBOutlet UITableViewCell *exportCell;
@property (weak, nonatomic) IBOutlet UITableViewCell *changeMPCell;
@property (weak, nonatomic) IBOutlet UILabel *defaultTypeLabel;
- (IBAction)didToggleSwitch:(UISwitch *)sender;
- (IBAction)settings:(id)sender;
@end

View File

@@ -0,0 +1,169 @@
//
// MPPreferencesViewController.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import "MPPreferencesViewController.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
@interface MPPreferencesViewController ()
@end
@implementation MPPreferencesViewController
@synthesize avatarsView;
@synthesize avatarTemplate;
@synthesize savePasswordSwitch;
@synthesize exportCell;
@synthesize changeMPCell;
@synthesize defaultTypeLabel;
- (void)viewDidLoad {
self.avatarTemplate.hidden = YES;
for (int a = 0; a < MPAvatarCount; ++a) {
UIButton *avatar = [self.avatarTemplate clone];
avatar.tag = a;
avatar.hidden = NO;
avatar.center = CGPointMake(
self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
self.avatarTemplate.center.y);
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)]
forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 5;
avatar.backgroundColor = [UIColor clearColor];
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected)
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
else
avatar.backgroundColor = [UIColor clearColor];
} options:0];
[avatar onSelect:^(BOOL selected) {
if (selected) {
[MPAppDelegate get].activeUser.avatar = (unsigned)avatar.tag;
[[MPAppDelegate get] saveContext];
}
} options:0];
avatar.selected = (a == [MPAppDelegate get].activeUser.avatar);
}
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
inf(@"Preferences will appear");
[self.avatarsView autoSizeContent];
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if (subview.tag && ((UIControl *)subview).selected) {
[self.avatarsView setContentOffset:CGPointMake(subview.center.x - self.avatarsView.bounds.size.width / 2, 0) animated:animated];
}
} recurse:NO];
self.savePasswordSwitch.on = [MPAppDelegate get].activeUser.saveKey;
self.defaultTypeLabel.text = NSStringShortFromMPElementType([MPAppDelegate get].activeUser.defaultType);
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Preferences will disappear");
[super viewWillDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)viewDidUnload {
[self setAvatarsView:nil];
[self setAvatarTemplate:nil];
[self setAvatarsView:nil];
[self setSavePasswordSwitch:nil];
[self setExportCell:nil];
[self setChangeMPCell:nil];
[self setDefaultTypeLabel:nil];
[super viewDidUnload];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:@"MP_ChooseType"])
((MPTypeViewController *)[segue destinationViewController]).delegate = self;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell == self.exportCell)
[[MPAppDelegate get] export];
else
if (cell == self.changeMPCell)
[[MPAppDelegate get] changeMasterPasswordFor:[MPAppDelegate get].activeUser];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - IASKSettingsDelegate
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
while ([self.navigationController.viewControllers containsObject:sender])
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark - MPTypeDelegate
- (void)didSelectType:(MPElementType)type {
[MPAppDelegate get].activeUser.defaultType = type;
[[MPAppDelegate get] saveContext];
self.defaultTypeLabel.text = NSStringShortFromMPElementType([MPAppDelegate get].activeUser.defaultType);
}
- (MPElementType)selectedType {
return [MPAppDelegate get].activeUser.defaultType;
}
#pragma mark - IBActions
- (IBAction)didToggleSwitch:(UISwitch *)sender {
if (([MPAppDelegate get].activeUser.saveKey = sender.on))
[[MPAppDelegate get] storeSavedKeyFor:[MPAppDelegate get].activeUser];
else
[[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser];
[[MPAppDelegate get] saveContext];
}
- (IBAction)settings:(UIBarButtonItem *)sender {
IASKAppSettingsViewController *vc = [IASKAppSettingsViewController new];
vc.showDoneButton = NO;
[self.navigationController pushViewController:vc animated:YES];
}
@end

View File

@@ -9,21 +9,21 @@
#import <Foundation/Foundation.h>
#import "MPElementEntity.h"
@protocol MPSearchResultsDelegate <NSObject>
@protocol MPSearchResultsDelegate<NSObject>
- (void)didSelectElement:(MPElementEntity *)element;
@end
@interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
@interface MPSearchDelegate : NSObject<UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSString *query;
@property (strong, nonatomic) UILabel *tipView;
@property (strong, nonatomic) NSString *query;
@property (strong, nonatomic) UILabel *tipView;
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@property (strong, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@end

View File

@@ -9,12 +9,11 @@
#import "MPSearchDelegate.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPElementGeneratedEntity.h"
#import "LocalyticsSession.h"
@interface MPSearchDelegate (Private)
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath;
- (void)update;
@end
@@ -28,48 +27,49 @@
@synthesize searchTipContainer;
- (id)init {
if (!([super init]))
return nil;
self.dateFormatter = [NSDateFormatter new];
self.dateFormatter = [NSDateFormatter new];
self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
self.query = @"";
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil];
self.query = @"";
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
self.tipView.textAlignment = UITextAlignmentCenter;
self.tipView.backgroundColor = [UIColor clearColor];
self.tipView.textColor = [UIColor lightTextColor];
self.tipView.shadowColor = [UIColor blackColor];
self.tipView.shadowOffset = CGSizeMake(0, -1);
self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
self.tipView.numberOfLines = 0;
self.tipView.font = [UIFont systemFontOfSize:14];
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
self.tipView.textAlignment = UITextAlignmentCenter;
self.tipView.backgroundColor = [UIColor clearColor];
self.tipView.textColor = [UIColor lightTextColor];
self.tipView.shadowColor = [UIColor blackColor];
self.tipView.shadowOffset = CGSizeMake(0, -1);
self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
| UIViewAutoresizingFlexibleBottomMargin;
self.tipView.numberOfLines = 0;
self.tipView.font = [UIFont systemFontOfSize:14];
self.tipView.text =
@"Tip:\n"
@"Name your sites by their domain name:\n"
@"apple.com, twitter.com\n\n"
@"For email accounts, use the address:\n"
@"john@apple.com, john@gmail.com";
@"Tip:\n"
@"Name your sites by their domain name:\n"
@"apple.com, twitter.com\n\n"
@"For email accounts, use the address:\n"
@"john@apple.com, john@gmail.com";
return self;
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
for (NSInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
NSInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
if (!rowCount)
continue;
if (rowCount == 1)
[self tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
break;
@@ -77,69 +77,65 @@
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
[TestFlight passCheckpoint:MPCheckpointCancelSearch];
[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]];
self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (!self.query)
self.query = @"";
}
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.prompt = @"Enter the site's name:";
[UIView animateWithDuration:0.2f animations:^{
self.searchTipContainer.alpha = 0;
}];
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
self.query = @"";
self.query = @"";
}
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
controller.searchBar.prompt = nil;
dbg(@"Search ended with: %@", controller.searchBar.text);
controller.searchBar.prompt = nil;
controller.searchBar.searchResultsButtonSelected = NO;
}
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
tableView.backgroundColor = [UIColor blackColor];
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView.rowHeight = 48.0f;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView.rowHeight = 48.0f;
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self update];
return NO;
}
- (void)update {
if (!controller.active)
return NO;
assert(self.query);
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND keyID == %@",
self.query, self.query, NilToNull([MPAppDelegate get].keyID)];
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
self.query, self.query, NilToNSNull([MPAppDelegate get].activeUser)];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
err(@"Couldn't fetch elements: %@", error);
[self.searchDisplayController.searchResultsTableView reloadData];
err(@"Couldn't fetch elements: %@", error);
NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews;
NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1;
UIView *overlay = [subviews count] > overlayIndex? [subviews objectAtIndex:overlayIndex]: nil;
@@ -149,6 +145,8 @@
[self.tipView removeFromSuperview];
[overlay addSubview:self.tipView];
}
return YES;
}
// See MP-14, also crashes easily on internal assertions etc..
@@ -205,150 +203,158 @@
//}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.searchDisplayController.searchResultsTableView reloadData];
// [self.searchDisplayController.searchResultsTableView endUpdates];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSArray *sections = [self.fetchedResultsController sections];
NSUInteger sectionCount = [sections count];
if ([self.query length]) {
__block BOOL hasExactQueryMatch = NO;
[sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id<NSFetchedResultsSectionInfo> sectionInfo = obj;
[[sectionInfo objects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([[obj name] isEqualToString:self.query]) {
[[sectionInfo objects] enumerateObjectsUsingBlock:^(id obj_, NSUInteger idx_, BOOL *stop_) {
if ([[obj_ name] isEqualToString:self.query]) {
hasExactQueryMatch = YES;
*stop = YES;
*stop_ = YES;
}
}];
if (hasExactQueryMatch)
*stop = YES;
*stop = YES;
}];
if (!hasExactQueryMatch)
// Add a section for "new site".
// Add a section for "new site".
++sectionCount;
}
return (signed)sectionCount;
return (NSInteger)sectionCount;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *sections = [self.fetchedResultsController sections];
if (section < (signed)[sections count])
return (signed)[[sections objectAtIndex:(unsigned)section] numberOfObjects];
if (section < (NSInteger)[sections count])
return (NSInteger)[[sections objectAtIndex:(unsigned)section] numberOfObjects];
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]];
backgroundImageView.frame = CGRectMake(-5, 0, 330, 34);
backgroundImageView.frame = CGRectMake(-5, 0, 330, 34);
backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f);
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.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.backgroundView = backgroundView;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor];
cell.backgroundView = backgroundView;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.clipsToBounds = YES;
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.clipsToBounds = YES;
}
[self configureCell:cell inTableView:tableView atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) {
if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) {
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = element.name;
cell.textLabel.text = element.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"Used %d times, last on %@",
element.uses, [self.dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:element.lastUsed]]];
element.uses, [self.dateFormatter stringFromDate:element.lastUsed]];
} else {
// "New" section
cell.textLabel.text = self.query;
cell.textLabel.text = self.query;
cell.detailTextLabel.text = @"Create a new site.";
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count])
if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count])
[self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]];
else {
// "New" section.
NSString *siteName = self.query;
[PearlAlert showAlertWithTitle:@"New Site"
message:PearlLocalize(@"Do you want to create a new site named:\n%@", siteName)
message:PearlString(@"Do you want to create a new site named:\n%@", siteName)
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (buttonIndex == [alert cancelButtonIndex])
return;
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]);
assert([MPAppDelegate get].keyID);
element.name = siteName;
element.keyID = [MPAppDelegate get].keyID;
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didSelectElement:element];
});
}];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (buttonIndex == [alert cancelButtonIndex])
return;
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementType type = [MPAppDelegate get].activeUser.defaultType;
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([MPAppDelegate get].activeUser);
element.name = siteName;
element.user = [MPAppDelegate get].activeUser;
element.type = type;
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didSelectElement:element];
});
}];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (section < (signed)[[self.fetchedResultsController sections] count])
if (section < (NSInteger)[[self.fetchedResultsController sections] count])
return [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name];
return @"";
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [self.fetchedResultsController sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) {
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) {
if (editingStyle == UITableViewCellEditingStyleDelete)
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
inf(@"Deleting element: %@", element.name);
[self.fetchedResultsController.managedObjectContext deleteObject:element];
[TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement];
[TestFlight passCheckpoint:MPCheckpointDeleteElement];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointDeleteElement
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(element.type), @"type",
nil]];
}];
}
}

View File

@@ -8,7 +8,7 @@
#import <UIKit/UIKit.h>
@protocol MPTypeDelegate <NSObject>
@protocol MPTypeDelegate<NSObject>
- (void)didSelectType:(MPElementType)type;
@@ -20,5 +20,6 @@
@interface MPTypeViewController : UITableViewController
@property (nonatomic, weak) id<MPTypeDelegate> delegate;
@property (weak, nonatomic) IBOutlet UIView *recommendedTipContainer;
@end

View File

@@ -17,95 +17,125 @@
@implementation MPTypeViewController
@synthesize delegate;
@synthesize recommendedTipContainer;
#pragma mark - View lifecycle
- (void)viewWillAppear:(BOOL)animated {
inf(@"Type selection will appear");
self.recommendedTipContainer.alpha = 0;
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
if ([[MPiOSConfig get].firstRun boolValue])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.recommendedTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{
self.recommendedTipContainer.alpha = 0;
}];
});
}
}];
[super viewDidAppear:animated];
}
- (void)viewDidLoad {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
[super viewDidLoad];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Type selection will disappear");
[super viewWillDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
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;
}];
cell.selected = ([delegate selectedType] == [self typeAtIndexPath:indexPath]);
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
assert(self.navigationController.topViewController == self);
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
[self.navigationController popViewControllerAnimated:YES];
}
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case 0: {
// Generated
switch (indexPath.row) {
case 0:
return MPElementTypeGeneratedLong;
return NSNotFound;
case 1:
return MPElementTypeGeneratedMedium;
return MPElementTypeGeneratedMaximum;
case 2:
return MPElementTypeGeneratedShort;
return MPElementTypeGeneratedLong;
case 3:
return MPElementTypeGeneratedBasic;
return MPElementTypeGeneratedMedium;
case 4:
return MPElementTypeGeneratedShort;
case 5:
return MPElementTypeGeneratedBasic;
case 6:
return MPElementTypeGeneratedPIN;
case 7:
return NSNotFound;
default:
[NSException raise:NSInternalInconsistencyException
format:@"Unsupported row: %d, when selecting generated element type.", indexPath.row];
Throw(@"Unsupported row: %d, when selecting generated element type.", indexPath.row);
}
break;
}
case 1: {
// Stored
switch (indexPath.row) {
case 0:
return MPElementTypeStoredPersonal;
return NSNotFound;
case 1:
return MPElementTypeStoredPersonal;
case 2:
return MPElementTypeStoredDevicePrivate;
case 3:
return NSNotFound;
default:
[NSException raise:NSInternalInconsistencyException
format:@"Unsupported row: %d, when selecting stored element type.", indexPath.row];
Throw(@"Unsupported row: %d, when selecting stored element type.", indexPath.row);
}
break;
}
default:
[NSException raise:NSInternalInconsistencyException
format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
Throw(@"Unsupported section: %d, when selecting element type.", indexPath.section);
}
@throw nil;
}
- (void)viewDidUnload {
[self setRecommendedTipContainer:nil];
[super viewDidUnload];
}
@end

View File

@@ -8,14 +8,22 @@
#import <UIKit/UIKit.h>
@interface MPUnlockViewController : UIViewController <UITextFieldDelegate>
@interface MPUnlockViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate>
@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;
@property (weak, nonatomic) IBOutlet UIImageView *spinner;
@property (weak, nonatomic) IBOutlet UITextField *passwordField;
@property (weak, nonatomic) IBOutlet UIView *passwordView;
@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *oldNameLabel;
@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate;
@property (weak, nonatomic) IBOutlet UILabel *deleteTip;
@property (weak, nonatomic) IBOutlet UIView *passwordTipView;
@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel;
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *targetedUserActionGesture;
- (IBAction)changeMP;
@property (nonatomic, strong) UIColor *avatarShadowColor;
- (IBAction)targetedUserAction:(UILongPressGestureRecognizer *)sender;
@end

View File

@@ -12,245 +12,625 @@
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "MPElementEntity.h"
typedef enum {
MPLockscreenIdle,
MPLockscreenError,
MPLockscreenSuccess,
MPLockscreenProgress,
} MPLockscreen;
@interface MPUnlockViewController ()
@property (strong, nonatomic) MPUserEntity *selectedUser;
@property (strong, nonatomic) NSMutableDictionary *avatarToUser;
@end
@implementation MPUnlockViewController
@synthesize lock;
@synthesize selectedUser;
@synthesize avatarToUser;
@synthesize spinner;
@synthesize field;
@synthesize messageLabel;
@synthesize changeMPView;
@synthesize passwordField;
@synthesize passwordView;
@synthesize avatarsView;
@synthesize nameLabel, oldNameLabel;
@synthesize avatarTemplate;
@synthesize deleteTip;
@synthesize passwordTipView;
@synthesize passwordTipLabel;
@synthesize targetedUserActionGesture;
@synthesize avatarShadowColor = _avatarShadowColor;
- (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)initAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
UIScrollView *alertAvatarScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(12, 30, 260, 150)];
alertAvatarScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
[alertAvatarScrollView flashScrollIndicatorsContinuously];
[alert addSubview:alertAvatarScrollView];
CGPoint selectedOffset = CGPointZero;
for (int a = 0; a < MPAvatarCount; ++a) {
UIButton *avatar = [self.avatarTemplate cloneAddedTo:alertAvatarScrollView];
avatar.tag = a;
avatar.hidden = NO;
avatar.center = CGPointMake(
(20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
20 + self.avatarTemplate.bounds.size.height / 2);
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)] forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 5;
avatar.backgroundColor = [UIColor clearColor];
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected)
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
else
avatar.backgroundColor = [UIColor clearColor];
} options:0];
[avatar onSelect:^(BOOL selected) {
if (selected)
user.avatar = (unsigned)avatar.tag;
} options:0];
avatar.selected = (a == user.avatar);
if (avatar.selected)
selectedOffset = CGPointMake(avatar.center.x - alertAvatarScrollView.bounds.size.width / 2, 0);
}
[alertAvatarScrollView autoSizeContent];
[alertAvatarScrollView setContentOffset:selectedOffset animated:YES];
}
- (void)hideMessage {
[UIView animateWithDuration:0.5f animations:^{
self.messageLabel.alpha = 0.0f;
}];
- (void)initConfirmationAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(12, 70, 260, 110)];
[alert addSubview:container];
UIButton *alertAvatar = [self.avatarTemplate cloneAddedTo:container];
alertAvatar.center = CGPointMake(130, 55);
alertAvatar.hidden = NO;
alertAvatar.layer.shadowColor = [UIColor blackColor].CGColor;
alertAvatar.layer.shadowOpacity = 1;
alertAvatar.layer.shadowRadius = 5;
alertAvatar.backgroundColor = [UIColor clearColor];
[alertAvatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", user.avatar)] forState:UIControlStateNormal];
UILabel *alertNameLabel = [self.nameLabel cloneAddedTo:container];
alertNameLabel.center = alertAvatar.center;
alertNameLabel.text = user.name;
alertNameLabel.bounds = CGRectSetHeight(alertNameLabel.bounds,
[alertNameLabel.text sizeWithFont:self.nameLabel.font
constrainedToSize:CGSizeMake(alertNameLabel.bounds.size.width - 10,
100)
lineBreakMode:self.nameLabel.lineBreakMode].height);
alertNameLabel.layer.cornerRadius = 5;
alertNameLabel.backgroundColor = [UIColor blackColor];
}
- (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];
}];
self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3];
self.avatarsView.decelerationRate = UIScrollViewDecelerationRateFast;
self.avatarsView.clipsToBounds = NO;
self.nameLabel.layer.cornerRadius = 5;
self.avatarTemplate.hidden = YES;
self.spinner.alpha = 0;
self.passwordTipView.alpha = 0;
[self updateLayoutAnimated:NO allowScroll:YES completion:nil];
[super viewDidLoad];
}
- (void)viewDidUnload {
[self setSpinner:nil];
[self setField:nil];
[self setMessageLabel:nil];
[self setLock:nil];
[self setChangeMPView:nil];
[self setPasswordField:nil];
[self setPasswordView:nil];
[self setAvatarsView:nil];
[self setNameLabel:nil];
[self setAvatarTemplate:nil];
[self setDeleteTip:nil];
[self setPasswordTipView:nil];
[self setPasswordTipLabel:nil];
[self setTargetedUserActionGesture:nil];
[super viewDidUnload];
}
- (void)viewWillAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:YES
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
inf(@"Lock screen will appear");
self.selectedUser = nil;
[self updateUsers];
[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];
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewDidAppear:animated];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if ([textField.text length]) {
[textField resignFirstResponder];
return YES;
}
return NO;
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Lock screen will disappear");
[super viewWillDisappear:animated];
}
- (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];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", [MPAppDelegate get].keyID];
fetchRequest.fetchLimit = 1;
BOOL keyIDHasElements = [[[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil] count] > 0;
if (keyIDHasElements)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
});
else {
[PearlAlert showAlertWithTitle:@"New Master Password"
message:
@"Please confirm the spelling of this new master password."
viewStyle:UIAlertViewStyleSecureTextInput
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) {
[[MPAppDelegate get] updateKey:nil];
return;
}
if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) {
[PearlAlert showAlertWithTitle:@"Incorrect Master Password"
message:
@"The password you entered doesn't match with the master password you tried to use. "
@"You've probably mistyped one of them.\n\n"
@"Give it another try."
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
return;
}
[self dismissModalViewControllerAnimated:YES];
}
cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
});
- (void)updateUsers {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil];
// Clean up avatars.
for (UIView *subview in [self.avatarsView subviews])
if ([[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
// This subview is a former avatar.
[subview removeFromSuperview];
[self.avatarToUser removeAllObjects];
// Create avatars.
for (MPUserEntity *user in users)
[self setupAvatar:[self.avatarTemplate clone] forUser:user];
[self setupAvatar:[self.avatarTemplate clone] forUser:nil];
// Scroll view's content changed, update its content size.
[self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
}
- (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user {
avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y);
avatar.hidden = NO;
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 20;
avatar.layer.masksToBounds = NO;
avatar.backgroundColor = [UIColor clearColor];
avatar.tag = user.avatar;
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)]
forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:YES];
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected)
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
else
avatar.backgroundColor = [UIColor clearColor];
} options:0];
[avatar onSelect:^(BOOL selected) {
if (selected) {
if ((self.selectedUser = user))
[self didToggleUserSelection];
else
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Not valid." state:MPLockscreenError];
[UIView animateWithDuration:0.5f animations:^{
self.changeMPView.alpha = 1.0f;
}];
});
[self didSelectNewUserAvatar:avatar];
} else {
self.selectedUser = nil;
[self didToggleUserSelection];
}
@finally {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 0.0f;
} completion:^(BOOL finished) {
[self.spinner.layer removeAllAnimations];
}];
});
} options:0];
[self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]];
if (self.selectedUser && user == self.selectedUser)
avatar.selected = YES;
return avatar;
}
- (void)didToggleUserSelection {
if (!self.selectedUser)
[self.passwordField resignFirstResponder];
else
if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) {
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
if (finished)
if (self.selectedUser)
[self.passwordField becomeFirstResponder];
}];
}
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
[self showNewUserNameAlertFor:newUser completion:^(BOOL finished){
newUserAvatar.selected = NO;
if (!finished)
[[MPAppDelegate managedObjectContext] deleteObject:newUser];
}];
}
- (void)showNewUserNameAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
[PearlAlert showAlertWithTitle:@"Enter Your Name"
message:nil viewStyle:UIAlertViewStylePlainTextInput
initAlert:^(UIAlertView *alert, UITextField *firstField) {
firstField.autocapitalizationType = UITextAutocapitalizationTypeWords;
firstField.keyboardType = UIKeyboardTypeAlphabet;
firstField.text = newUser.name;
firstField.placeholder = @"eg. Robert Lee Mitchell";
}
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) {
completion(NO);
return;
}
// Save
newUser.name = [alert textFieldAtIndex:0].text;
[self showNewUserAvatarAlertFor:newUser completion:completion];
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
}
- (void)showNewUserAvatarAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
[PearlAlert showAlertWithTitle:@"Choose Your Avatar"
message:@"\n\n\n\n\n\n" viewStyle:UIAlertViewStyleDefault
initAlert:^(UIAlertView *_alert, UITextField *_firstField) {
[self initAvatarAlert:_alert forUser:newUser];
}
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
// Okay
[self showNewUserConfirmationAlertFor:newUser completion:completion];
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
}
- (void)showNewUserConfirmationAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
[PearlAlert showAlertWithTitle:@"Is this correct?"
message:
@"Please double-check your name.\n"
@"\n\n\n\n\n\n"
viewStyle:UIAlertViewStyleDefault
initAlert:^void(UIAlertView *__alert, UITextField *__firstField) {
[self initConfirmationAlert:__alert forUser:newUser];
}
tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) {
if (__buttonIndex == [__alert cancelButtonIndex]) {
[self showNewUserNameAlertFor:newUser completion:completion];
return;
}
// Confirm
completion(YES);
self.selectedUser = newUser;
[self updateUsers];
}
cancelTitle:@"Change" otherTitles:@"Confirm", nil];
}
- (void)updateLayoutAnimated:(BOOL)animated allowScroll:(BOOL)allowScroll completion:(void (^)(BOOL finished))completion {
if (animated) {
self.oldNameLabel.text = self.nameLabel.text;
self.oldNameLabel.alpha = 1;
self.nameLabel.alpha = 0;
[UIView animateWithDuration:0.5f animations:^{
[self updateLayoutAnimated:NO allowScroll:allowScroll completion:nil];
self.oldNameLabel.alpha = 0;
self.nameLabel.alpha = 1;
} completion:^(BOOL finished) {
if (completion)
completion(finished);
}];
return;
}
if (self.selectedUser && !self.passwordView.alpha) {
self.passwordView.alpha = 1;
self.avatarsView.center = CGPointMake(160, 170);
self.avatarsView.scrollEnabled = NO;
self.nameLabel.center = CGPointMake(160, 84);
self.nameLabel.backgroundColor = [UIColor blackColor];
self.oldNameLabel.center = self.nameLabel.center;
self.avatarShadowColor = [UIColor whiteColor];
self.deleteTip.alpha = 0;
} else
if (!self.selectedUser && self.passwordView.alpha == 1) {
self.passwordField.text = nil;
self.passwordView.alpha = 0;
self.avatarsView.center = CGPointMake(160, 310);
self.avatarsView.scrollEnabled = YES;
self.nameLabel.center = CGPointMake(160, 296);
self.nameLabel.backgroundColor = [UIColor clearColor];
self.oldNameLabel.center = self.nameLabel.center;
self.avatarShadowColor = [UIColor lightGrayColor];
self.deleteTip.alpha = 0.5;
}
MPUserEntity *targetedUser = self.selectedUser;
UIButton *selectedAvatar = [self avatarForUser:self.selectedUser];
UIButton *targetedAvatar = selectedAvatar;
if (!targetedAvatar) {
targetedAvatar = [self findTargetedAvatar];
targetedUser = [self userForAvatar:targetedAvatar];
}
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if (![[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
// This subview is not one of the user avatars.
return;
UIButton *avatar = (UIButton *)subview;
BOOL isTargeted = avatar == targetedAvatar;
avatar.userInteractionEnabled = isTargeted;
avatar.alpha = isTargeted? 1: self.selectedUser? 0.1: 0.4;
[self updateAvatarShadowColor:avatar isTargeted:isTargeted];
} recurse:NO];
if (allowScroll) {
CGPoint targetContentOffset = CGPointMake(MAX(0, targetedAvatar.center.x - self.avatarsView.bounds.size.width / 2),
self.avatarsView.contentOffset.y);
if (!CGPointEqualToPoint(self.avatarsView.contentOffset, targetContentOffset))
[self.avatarsView setContentOffset:targetContentOffset animated:animated];
}
self.nameLabel.text = targetedUser? targetedUser.name: @"New User";
self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds,
[self.nameLabel.text sizeWithFont:self.nameLabel.font
constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100)
lineBreakMode:self.nameLabel.lineBreakMode].height);
self.oldNameLabel.bounds = self.nameLabel.bounds;
if (completion)
completion(YES);
}
- (void)setPasswordTip:(NSString *)string {
if (string.length)
self.passwordTipLabel.text = string;
[UIView animateWithDuration:0.3f animations:^{
self.passwordTipView.alpha = string.length? 1: 0;
}];
}
- (void)tryMasterPassword {
[self setSpinnerActive:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text];
dispatch_async(dispatch_get_main_queue(), ^{
if (unlocked)
[self dismissViewControllerAnimated:YES completion:nil];
else {
if (self.passwordField.text.length)
[self setPasswordTip:@"Incorrect password."];
[self setSpinnerActive:NO];
}
});
});
}
- (IBAction)changeMP {
[PearlAlert showAlertWithTitle:@"Changing Master Password"
message:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[[MPAppDelegate get] forgetKey];
[[MPAppDelegate get] loadKey:YES];
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];
- (UIButton *)findTargetedAvatar {
CGFloat xOfMiddle = self.avatarsView.contentOffset.x + self.avatarsView.bounds.size.width / 2;
return (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, self.avatarsView.contentOffset.y)
ofArray:self.avatarsView.subviews];
}
- (UIButton *)avatarForUser:(MPUserEntity *)user {
__block UIButton *avatar = nil;
if (user)
[self.avatarToUser enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (obj == user)
avatar = [key nonretainedObjectValue];
}];
return avatar;
}
- (MPUserEntity *)userForAvatar:(UIButton *)avatar {
return NSNullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]);
}
- (void)setSpinnerActive:(BOOL)active {
PearlMainThread(^{
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.toValue = [NSNumber numberWithDouble:2 * M_PI];
rotate.duration = 5.0;
if (active) {
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.repeatCount = MAXFLOAT;
} else {
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
rotate.repeatCount = 1;
}
[self.spinner.layer removeAnimationForKey:@"rotation"];
[self.spinner.layer addAnimation:rotate forKey:@"rotation"];
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = active? 1: 0;
if (active)
[self avatarForUser:self.selectedUser].backgroundColor = [UIColor clearColor];
else
[self avatarForUser:self.selectedUser].backgroundColor = self.avatarTemplate.backgroundColor;
}];
});
}
- (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted {
if (targeted) {
if (![avatar.layer animationForKey:@"targetedShadow"]) {
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
toShadowColorAnimation.toValue = (__bridge id)(avatar.selected? self.avatarTemplate.backgroundColor
: [UIColor whiteColor]).CGColor;
toShadowColorAnimation.beginTime = 0.0f;
toShadowColorAnimation.duration = 0.5f;
toShadowColorAnimation.fillMode = kCAFillModeForwards;
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
toShadowOpacityAnimation.toValue = PearlFloat(0.2);
toShadowOpacityAnimation.duration = 0.5f;
CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
pulseShadowOpacityAnimation.fromValue = PearlFloat(0.2);
pulseShadowOpacityAnimation.toValue = PearlFloat(0.6);
pulseShadowOpacityAnimation.beginTime = 0.5f;
pulseShadowOpacityAnimation.duration = 2.0f;
pulseShadowOpacityAnimation.autoreverses = YES;
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation, nil];
group.duration = MAXFLOAT;
[avatar.layer removeAnimationForKey:@"inactiveShadow"];
[avatar.layer addAnimation:group forKey:@"targetedShadow"];
}
} else {
if ([avatar.layer animationForKey:@"targetedShadow"]) {
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
toShadowColorAnimation.toValue = (__bridge id)[UIColor blackColor].CGColor;
toShadowColorAnimation.duration = 0.5f;
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
toShadowOpacityAnimation.toValue = PearlFloat(1);
toShadowOpacityAnimation.duration = 0.5f;
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, nil];
group.duration = 0.5f;
[avatar.layer removeAnimationForKey:@"targetedShadow"];
[avatar.layer addAnimation:group forKey:@"inactiveShadow"];
}
}
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UITextField *)textField {
[self setPasswordTip:nil];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
[self setSpinnerActive:YES];
if (self.selectedUser.keyID)
[self tryMasterPassword];
else
[PearlAlert showAlertWithTitle:@"New Master Password"
message:@"Please confirm the spelling of this new master password."
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[self setSpinnerActive:NO];
if (buttonIndex == [alert cancelButtonIndex])
return;
if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) {
[PearlAlert showAlertWithTitle:@"Incorrect Master Password"
message:
@"The password you entered doesn't match with the master password you tried to use. "
@"You've probably mistyped one of them.\n\n"
@"Give it another try."
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
return;
}
[self tryMasterPassword];
}
cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:[PearlStrings get].commonButtonContinue, nil];
return YES;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset {
CGFloat xOfMiddle = targetContentOffset->x + scrollView.bounds.size.width / 2;
UIButton *middleAvatar = (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, targetContentOffset->y)
ofArray:scrollView.subviews];
*targetContentOffset = CGPointMake(middleAvatar.center.x - scrollView.bounds.size.width / 2, targetContentOffset->y);
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
}
#pragma mark - IBActions
- (IBAction)targetedUserAction:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan)
return;
if (self.selectedUser)
return;
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]];
if (!targetedUser)
return;
[PearlSheet showSheetWithTitle:targetedUser.name
message:nil
viewStyle:UIActionSheetStyleBlackTranslucent
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
if (buttonIndex == [sheet destructiveButtonIndex]) {
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
[[MPAppDelegate get] saveContext];
[self updateUsers];
} else if (buttonIndex == [sheet firstOtherButtonIndex])
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser];
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
}
@end

View File

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

View File

@@ -6,27 +6,26 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPiOSConfig.h"
@implementation MPiOSConfig
@dynamic helpHidden, showQuickStart;
@dynamic sendInfo, helpHidden, showQuickStart;
- (id)init {
if(!(self = [super init]))
if (!(self = [super init]))
return self;
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
@"510296984", NSStringFromSelector(@selector(iTunesID)),
nil]];
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(sendInfo)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
@"510296984", NSStringFromSelector(@selector(iTunesID)),
nil]];
return self;
}
+ (MPiOSConfig *)get {
return (MPiOSConfig *)[super get];
}

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,15 @@
<true/>
<key>NSHumanReadableCopyright</key>
<string>© 2011-2012, Lyndir</string>
<key>ReplacementFonts</key>
<dict>
<key>Futura-CondensedExtraBold</key>
<string>Exo-ExtraBold</string>
<key>Futura-CondensedMedium</key>
<string>Exo-Light</string>
<key>Futura-Medium</key>
<string>Exo</string>
</dict>
<key>UIAppFonts</key>
<array>
<string>Exo-Black.otf</string>

View File

@@ -11,12 +11,18 @@
#import "Pearl-Prefix.pch"
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "TestFlight.h"
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "TestFlight.h"
#import <Crashlytics/Crashlytics.h>
//#define Crashlytics PearlNil
//#define TestFlight PearlNil
#import "MPTypes.h"
#import "MPiOSConfig.h"
#import "MPTypes.h"
#import "MPiOSConfig.h"
#endif

View File

@@ -6,7 +6,7 @@
<array>
<dict>
<key>FooterText</key>
<string>http://masterpassword.lyndir.com</string>
<string>If you&apos;re experiencing problems, enabling this will send us details that can help diagnose and resolve them. Great care has been taken to guarantee no private information is ever sent.</string>
<key>Title</key>
<string></string>
<key>Type</key>
@@ -42,9 +42,19 @@
<key>Key</key>
<string>unset</string>
</dict>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Send Diagnostic Info</string>
<key>Key</key>
<string>sendInfo</string>
<key>DefaultValue</key>
<false/>
</dict>
<dict>
<key>FooterText</key>
<string>This causes your master password to be remembered while your device is powered on. Similar to your phone&apos;s SIM lock, you only need to enter the password once after powering on.</string>
<string>When enabled, closing the application will not log out the user. Similar to your phone&apos;s SIM lock, you only need to log in once after powering on.</string>
<key>Title</key>
<string>Master Password</string>
<key>Type</key>
@@ -54,27 +64,9 @@
<key>DefaultValue</key>
<false/>
<key>Key</key>
<string>rememberKey</string>
<string>rememberLogin</string>
<key>Title</key>
<string>Remember my password</string>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Use this to save your master password in the key chain. This is somewhat less secure should your device get stolen, but it means you won&apos;t need to enter the master password anymore. You can compensate for the reduced security by going into General-&gt;Passcode Lock, disabling &quot;Simple Passcode&quot;, and setting a more secure passcode for your device.</string>
<key>Title</key>
<string></string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>DefaultValue</key>
<false/>
<key>Key</key>
<string>saveKey</string>
<key>Title</key>
<string>Save my password</string>
<string>Stay logged in</string>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
</dict>

View File

@@ -6,12 +6,10 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "MPAppDelegate.h"
int main(int argc, char *argv[])
{
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MPAppDelegate class]));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

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