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 url = git://github.com/futuretap/InAppSettingsKit.git
[submodule "External/iCloudStoreManager"] [submodule "External/iCloudStoreManager"]
path = 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> #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; @protocol CrashlyticsDelegate;
@interface Crashlytics : NSObject { @interface Crashlytics : NSObject {
@@ -63,6 +103,49 @@
**/ **/
- (void)crash; - (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 @end
/** /**
@@ -80,7 +163,7 @@
* *
* Called once a Crashlytics instance has determined that the last execution of the * 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 * 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. * startWithAPIKey:... calls, this will take at least that long to be invoked.
* *
**/ **/

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.1.3</string> <string>1.1.2</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
@@ -25,7 +25,7 @@
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>0100.01.03</string> <string>0101.02.00</string>
<key>CrashlyticsAPIKey</key> <key>CrashlyticsAPIKey</key>
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string> <string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
<key>DTCompiler</key> <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 */; }; DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; }; DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; };
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.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 */; }; 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 */; }; 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 */; }; DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA4426001557BF260052177D /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DA4425F11557BF260052177D /* UbiquityStoreManager.h */; }; DA4426001557BF260052177D /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DA4425F11557BF260052177D /* UbiquityStoreManager.h */; };
DA4426011557BF260052177D /* UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4425F21557BF260052177D /* UbiquityStoreManager.m */; }; 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 */; }; DA600C2815056428008E9AB6 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA600C2715056427008E9AB6 /* MPConfig.m */; };
DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; }; DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; };
DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; 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 */; }; DA95D59D14DF063C008D1B94 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA95D5CF14DF0691008D1B94 /* IASKAppSettingsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */; }; DA95D5CF14DF0691008D1B94 /* IASKAppSettingsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */; };
DA95D5D014DF0691008D1B94 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */; }; 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 */; }; DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; }; DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.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 */; }; DAC6327B1486809A0075AEA5 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC632791486809A0075AEA5 /* JRSwizzle.h */; };
DAC6327C1486809A0075AEA5 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC6327A1486809A0075AEA5 /* JRSwizzle.m */; }; DAC6327C1486809A0075AEA5 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC6327A1486809A0075AEA5 /* JRSwizzle.m */; };
DAC632891486D9690075AEA5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; }; 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 */; }; 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 */; }; DAD3126715528C9C00A3F9ED /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3125F15528C9C00A3F9ED /* Crashlytics.framework */; };
DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */ = {isa = PBXBuildFile; fileRef = DAD3126015528C9C00A3F9ED /* Crashlytics.plist */; }; DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */ = {isa = PBXBuildFile; fileRef = DAD3126015528C9C00A3F9ED /* Crashlytics.plist */; };
DAD3126915528C9C00A3F9ED /* libTestFlight.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3126215528C9C00A3F9ED /* libTestFlight.a */; }; 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 */; }; DAD312BE1552977200A3F9ED /* UIColor+HSV.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD312BA1552977200A3F9ED /* UIColor+HSV.m */; };
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3127115528CD200A3F9ED /* libLocalytics.a */; }; DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3127115528CD200A3F9ED /* libLocalytics.a */; };
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD312C01552A20800A3F9ED /* libsqlite3.dylib */; }; 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 */; }; DAEBC45314F6364500987BF6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; };
DAFE4A1315039824003ABA7C /* NSObject_PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DD15039823003ABA7C /* NSString+PearlSEL.m */; };
DAFE4A1915039824003ABA7C /* Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DE15039823003ABA7C /* Pearl.h */; }; DAFE4A1915039824003ABA7C /* Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DE15039823003ABA7C /* Pearl.h */; };
DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */; }; DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */; };
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */; }; 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 */; }; DAFE4A5515039824003ABA7C /* PearlValidatingTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE462115039823003ABA7C /* PearlValidatingTextField.m */; };
DAFE4A5615039824003ABA7C /* PearlWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE462215039823003ABA7C /* PearlWebViewController.h */; }; DAFE4A5615039824003ABA7C /* PearlWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE462215039823003ABA7C /* PearlWebViewController.h */; };
DAFE4A5715039824003ABA7C /* PearlWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE462315039823003ABA7C /* PearlWebViewController.m */; }; DAFE4A5715039824003ABA7C /* PearlWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE462315039823003ABA7C /* PearlWebViewController.m */; };
DAFE4A5815039824003ABA7C /* UIImage_PearlScaling.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A1115039824003ABA7C /* UIImage_PearlScaling.h */; }; 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 */; }; DAFE4A5915039824003ABA7C /* UIImage+PearlScaling.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A1215039824003ABA7C /* UIImage+PearlScaling.m */; };
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAFE45FA15039823003ABA7C /* Pearl.strings */; }; DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAFE45FA15039823003ABA7C /* Pearl.strings */; };
DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */; }; DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */; };
DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy 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; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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; }; 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; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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; }; 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; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -1731,6 +1799,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */,
DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */, DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */,
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */, DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */,
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */, DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */,
@@ -1751,6 +1820,14 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
DA829E4E159847E0002417D3 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA95D59914DF063C008D1B94 /* Frameworks */ = { DA95D59914DF063C008D1B94 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -1822,6 +1899,7 @@
DAD3126115528C9C00A3F9ED /* TestFlight */, DAD3126115528C9C00A3F9ED /* TestFlight */,
DAD3127315528CD200A3F9ED /* Localytics */, DAD3127315528CD200A3F9ED /* Localytics */,
DA4425D71557BF260052177D /* iCloudStoreManager */, DA4425D71557BF260052177D /* iCloudStoreManager */,
DA829E5D15984812002417D3 /* FontReplacer */,
DA5BFA47147E415C00F98B1E /* Frameworks */, DA5BFA47147E415C00F98B1E /* Frameworks */,
DA5BFA45147E415C00F98B1E /* Products */, DA5BFA45147E415C00F98B1E /* Products */,
); );
@@ -1837,6 +1915,7 @@
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */, DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */,
DAD3127115528CD200A3F9ED /* libLocalytics.a */, DAD3127115528CD200A3F9ED /* libLocalytics.a */,
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */, DA4425CB1557BED40052177D /* libiCloudStoreManager.a */,
DA829E51159847E0002417D3 /* libFontReplacer.a */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1844,6 +1923,7 @@
DA5BFA47147E415C00F98B1E /* Frameworks */ = { DA5BFA47147E415C00F98B1E /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */,
DAD312C01552A20800A3F9ED /* libsqlite3.dylib */, DAD312C01552A20800A3F9ED /* libsqlite3.dylib */,
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */, DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */,
DABB981515100B4000B05417 /* SystemConfiguration.framework */, DABB981515100B4000B05417 /* SystemConfiguration.framework */,
@@ -1862,8 +1942,17 @@
DA5BFA50147E415C00F98B1E /* MasterPassword */ = { DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( 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 */, DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
DAB8D43E15036BCF00CED3BC /* iOS */,
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */, DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */, DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */, DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
@@ -1872,14 +1961,9 @@
DA4426071557C1990052177D /* MPAppDelegate_Store.m */, DA4426071557C1990052177D /* MPAppDelegate_Store.m */,
DA600C2615056427008E9AB6 /* MPConfig.h */, DA600C2615056427008E9AB6 /* MPConfig.h */,
DA600C2715056427008E9AB6 /* MPConfig.m */, DA600C2715056427008E9AB6 /* MPConfig.m */,
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */,
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */,
DAB8D45915036BCF00CED3BC /* MPTypes.h */, DAB8D45915036BCF00CED3BC /* MPTypes.h */,
DAB8D45615036BCF00CED3BC /* MPTypes.m */, DAB8D45615036BCF00CED3BC /* MPTypes.m */,
DAB8D45715036BCF00CED3BC /* MPElementEntity.h */, DAB8D43E15036BCF00CED3BC /* iOS */,
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */,
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */,
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */,
); );
path = MasterPassword; path = MasterPassword;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1894,6 +1978,61 @@
name = Products; name = Products;
sourceTree = "<group>"; 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 */ = { DA95D59E14DF063C008D1B94 /* InAppSettingsKit */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1986,6 +2125,8 @@
DAB8D44B15036BCF00CED3BC /* MPGuideViewController.m */, DAB8D44B15036BCF00CED3BC /* MPGuideViewController.m */,
DAB8D44C15036BCF00CED3BC /* MPMainViewController.h */, DAB8D44C15036BCF00CED3BC /* MPMainViewController.h */,
DAB8D44D15036BCF00CED3BC /* MPMainViewController.m */, DAB8D44D15036BCF00CED3BC /* MPMainViewController.m */,
DAC728C8157C247B00889EF2 /* MPPreferencesViewController.h */,
DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */,
DAB8D44E15036BCF00CED3BC /* MPSearchDelegate.h */, DAB8D44E15036BCF00CED3BC /* MPSearchDelegate.h */,
DAB8D44F15036BCF00CED3BC /* MPSearchDelegate.m */, DAB8D44F15036BCF00CED3BC /* MPSearchDelegate.m */,
DAB8D45015036BCF00CED3BC /* MPTypeViewController.h */, DAB8D45015036BCF00CED3BC /* MPTypeViewController.h */,
@@ -2000,6 +2141,8 @@
DAB8D46F15036BF600CED3BC /* Resources */ = { DAB8D46F15036BF600CED3BC /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA902BD01576CA4A00C38161 /* keypad.png */,
DA902B931576C0FB00C38161 /* Avatars */,
DAB8D47015036BF600CED3BC /* Automaton */, DAB8D47015036BF600CED3BC /* Automaton */,
DAB8D4CE15036BF600CED3BC /* Background */, DAB8D4CE15036BF600CED3BC /* Background */,
DAB8D4D215036BF600CED3BC /* ciphers.plist */, DAB8D4D215036BF600CED3BC /* ciphers.plist */,
@@ -2637,12 +2780,10 @@
DAB8D6B715036BF600CED3BC /* Tooltips */ = { DAB8D6B715036BF600CED3BC /* Tooltips */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA0A1D0715690AD40092735D /* tip_arrow_banana.png */, DACABB8E1572B769008BA211 /* tip_basic_black_top.png */,
DA0A1D0815690AD40092735D /* tip_arrow_black.png */, DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */,
DA0A1D0915690AD40092735D /* tip_arrow_gray.png */, DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */,
DA0A1D0A15690AD40092735D /* tip_arrow_mercury.png */, DACABB8B1572A4A4008BA211 /* tip_basic_black_top_right@2x.png */,
DA0A1D0B15690AD40092735D /* tip_arrow_teal.png */,
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */,
DAB8D6B815036BF600CED3BC /* tip_alert_banana.png */, DAB8D6B815036BF600CED3BC /* tip_alert_banana.png */,
DAB8D6B915036BF600CED3BC /* tip_alert_banana@2x.png */, DAB8D6B915036BF600CED3BC /* tip_alert_banana@2x.png */,
DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */, DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */,
@@ -2655,11 +2796,17 @@
DAB8D6C115036BF600CED3BC /* tip_alert_teal@2x.png */, DAB8D6C115036BF600CED3BC /* tip_alert_teal@2x.png */,
DAB8D6C215036BF600CED3BC /* tip_alert_wood.png */, DAB8D6C215036BF600CED3BC /* tip_alert_wood.png */,
DAB8D6C315036BF600CED3BC /* tip_alert_wood@2x.png */, DAB8D6C315036BF600CED3BC /* tip_alert_wood@2x.png */,
DA0A1D0715690AD40092735D /* tip_arrow_banana.png */,
DAB8D6C415036BF600CED3BC /* tip_arrow_banana@2x.png */, DAB8D6C415036BF600CED3BC /* tip_arrow_banana@2x.png */,
DA0A1D0815690AD40092735D /* tip_arrow_black.png */,
DAB8D6C515036BF600CED3BC /* tip_arrow_black@2x.png */, DAB8D6C515036BF600CED3BC /* tip_arrow_black@2x.png */,
DA0A1D0915690AD40092735D /* tip_arrow_gray.png */,
DAB8D6C615036BF600CED3BC /* tip_arrow_gray@2x.png */, DAB8D6C615036BF600CED3BC /* tip_arrow_gray@2x.png */,
DA0A1D0A15690AD40092735D /* tip_arrow_mercury.png */,
DAB8D6C715036BF600CED3BC /* tip_arrow_mercury@2x.png */, DAB8D6C715036BF600CED3BC /* tip_arrow_mercury@2x.png */,
DA0A1D0B15690AD40092735D /* tip_arrow_teal.png */,
DAB8D6C815036BF600CED3BC /* tip_arrow_teal@2x.png */, DAB8D6C815036BF600CED3BC /* tip_arrow_teal@2x.png */,
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */,
DAB8D6C915036BF600CED3BC /* tip_arrow_wood@2x.png */, DAB8D6C915036BF600CED3BC /* tip_arrow_wood@2x.png */,
DAB8D6CA15036BF600CED3BC /* tip_basic_banana.png */, DAB8D6CA15036BF600CED3BC /* tip_basic_banana.png */,
DAB8D6CB15036BF600CED3BC /* tip_basic_banana@2x.png */, DAB8D6CB15036BF600CED3BC /* tip_basic_banana@2x.png */,
@@ -2823,13 +2970,20 @@
DAFE45D715039823003ABA7C /* Pearl */ = { DAFE45D715039823003ABA7C /* Pearl */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */, DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */,
DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */, DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */,
DAFE45DA15039823003ABA7C /* NSString_PearlNSArrayFormat.h */, DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */,
DAFE45DB15039823003ABA7C /* NSString_PearlNSArrayFormat.m */, DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */,
DAFE45DC15039823003ABA7C /* NSString_PearlSEL.h */, DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */,
DAFE45DD15039823003ABA7C /* NSString_PearlSEL.m */, 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 */, DAFE45DE15039823003ABA7C /* Pearl.h */,
DA30E9CD15722ECA00A68B4C /* Pearl.m */,
DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */, DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */,
DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */, DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */,
DAFE45E315039823003ABA7C /* PearlCodeUtils.h */, DAFE45E315039823003ABA7C /* PearlCodeUtils.h */,
@@ -2840,6 +2994,8 @@
DAFE45E815039823003ABA7C /* PearlDeviceUtils.m */, DAFE45E815039823003ABA7C /* PearlDeviceUtils.m */,
DAFE45E915039823003ABA7C /* PearlInfoPlist.h */, DAFE45E915039823003ABA7C /* PearlInfoPlist.h */,
DAFE45EA15039823003ABA7C /* PearlInfoPlist.m */, DAFE45EA15039823003ABA7C /* PearlInfoPlist.m */,
DA30E9D515723E6900A68B4C /* PearlLazy.h */,
DA30E9D615723E6900A68B4C /* PearlLazy.m */,
DAFE45EB15039823003ABA7C /* PearlLogger.h */, DAFE45EB15039823003ABA7C /* PearlLogger.h */,
DAFE45EC15039823003ABA7C /* PearlLogger.m */, DAFE45EC15039823003ABA7C /* PearlLogger.m */,
DAFE45ED15039823003ABA7C /* PearlMathUtils.h */, DAFE45ED15039823003ABA7C /* PearlMathUtils.h */,
@@ -2871,6 +3027,7 @@
children = ( children = (
DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */, DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */,
DAFE45FD15039823003ABA7C /* Pearl-Crypto.h */, DAFE45FD15039823003ABA7C /* Pearl-Crypto.h */,
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */,
DAFE45FE15039823003ABA7C /* PearlCryptUtils.h */, DAFE45FE15039823003ABA7C /* PearlCryptUtils.h */,
DAFE45FF15039823003ABA7C /* PearlCryptUtils.m */, DAFE45FF15039823003ABA7C /* PearlCryptUtils.m */,
DAFE460015039823003ABA7C /* PearlKeyChain.h */, DAFE460015039823003ABA7C /* PearlKeyChain.h */,
@@ -2888,8 +3045,15 @@
DAFE460715039823003ABA7C /* Pearl-UIKit */ = { DAFE460715039823003ABA7C /* Pearl-UIKit */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( 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 */, DAFE460815039823003ABA7C /* Pearl-UIKit-Dependencies.h */,
DAFE460915039823003ABA7C /* Pearl-UIKit.h */, DAFE460915039823003ABA7C /* Pearl-UIKit.h */,
DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */,
DAFE460A15039823003ABA7C /* PearlAlert.h */, DAFE460A15039823003ABA7C /* PearlAlert.h */,
DAFE460B15039823003ABA7C /* PearlAlert.m */, DAFE460B15039823003ABA7C /* PearlAlert.m */,
DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */, DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */,
@@ -2920,8 +3084,8 @@
DAFE462315039823003ABA7C /* PearlWebViewController.m */, DAFE462315039823003ABA7C /* PearlWebViewController.m */,
DAFE462415039823003ABA7C /* README */, DAFE462415039823003ABA7C /* README */,
DAFE462515039823003ABA7C /* Resources */, DAFE462515039823003ABA7C /* Resources */,
DAFE4A1115039824003ABA7C /* UIImage_PearlScaling.h */, DAFE4A1115039824003ABA7C /* UIImage+PearlScaling.h */,
DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */, DAFE4A1215039824003ABA7C /* UIImage+PearlScaling.m */,
); );
name = "Pearl-UIKit"; name = "Pearl-UIKit";
path = "External/Pearl/Pearl-UIKit"; path = "External/Pearl/Pearl-UIKit";
@@ -2945,6 +3109,14 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
DA829E4F159847E0002417D3 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DA829E6015984813002417D3 /* UIFont+Replacement.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA95D59A14DF063C008D1B94 /* Headers */ = { DA95D59A14DF063C008D1B94 /* Headers */ = {
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -2988,9 +3160,9 @@
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
DAFE4A1315039824003ABA7C /* NSObject_PearlExport.h in Headers */, DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */,
DAFE4A1515039824003ABA7C /* NSString_PearlNSArrayFormat.h in Headers */, DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */,
DAFE4A1715039824003ABA7C /* NSString_PearlSEL.h in Headers */, DAFE4A1715039824003ABA7C /* NSString+PearlSEL.h in Headers */,
DAFE4A1915039824003ABA7C /* Pearl.h in Headers */, DAFE4A1915039824003ABA7C /* Pearl.h in Headers */,
DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */, DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */,
DAFE4A1E15039824003ABA7C /* PearlCodeUtils.h in Headers */, DAFE4A1E15039824003ABA7C /* PearlCodeUtils.h in Headers */,
@@ -3023,8 +3195,15 @@
DAFE4A5215039824003ABA7C /* PearlUIUtils.h in Headers */, DAFE4A5215039824003ABA7C /* PearlUIUtils.h in Headers */,
DAFE4A5415039824003ABA7C /* PearlValidatingTextField.h in Headers */, DAFE4A5415039824003ABA7C /* PearlValidatingTextField.h in Headers */,
DAFE4A5615039824003ABA7C /* PearlWebViewController.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 */, 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; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -3079,6 +3258,23 @@
productReference = DA5BFA44147E415C00F98B1E /* MasterPassword.app */; productReference = DA5BFA44147E415C00F98B1E /* MasterPassword.app */;
productType = "com.apple.product-type.application"; 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 */ = { DA95D59B14DF063C008D1B94 /* InAppSettingsKit */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */; buildConfigurationList = DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */;
@@ -3203,6 +3399,7 @@
DA95D59B14DF063C008D1B94 /* InAppSettingsKit */, DA95D59B14DF063C008D1B94 /* InAppSettingsKit */,
DAD3127015528CD200A3F9ED /* Localytics */, DAD3127015528CD200A3F9ED /* Localytics */,
DA4425CA1557BED40052177D /* iCloudStoreManager */, DA4425CA1557BED40052177D /* iCloudStoreManager */,
DA829E50159847E0002417D3 /* FontReplacer */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@@ -3817,86 +4014,60 @@
DAB8D93715036BF700CED3BC /* lock_red.png in Resources */, DAB8D93715036BF700CED3BC /* lock_red.png in Resources */,
DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */, DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */,
DAB8D93915036BF700CED3BC /* logo-bare.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 */, DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */,
DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */, DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */,
DAD3126C15528C9C00A3F9ED /* TestFlight.plist in Resources */, DAD3126C15528C9C00A3F9ED /* TestFlight.plist in Resources */,
DAD3129015528D1600A3F9ED /* Localytics.plist in Resources */, DAD3129015528D1600A3F9ED /* Localytics.plist in Resources */,
DA0A1D0515690A9A0092735D /* Default.png in Resources */, DA0A1D0515690A9A0092735D /* Default.png in Resources */,
DA0A1D0615690A9A0092735D /* Default@2x.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 */, DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */,
DA0A1D1615690AF40092735D /* Icon-Small-50@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; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -3915,7 +4086,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = "/bin/bash -e"; 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; showEnvVarsInLog = 0;
}; };
DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */ = { DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */ = {
@@ -3957,14 +4128,25 @@
DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */, DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */, DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */, DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
DAB8D46B15036BCF00CED3BC /* MPElementStoredEntity.m in Sources */,
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */, DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
DAB8D46D15036BCF00CED3BC /* MPElementEntity.m in Sources */,
DAB8D46E15036BCF00CED3BC /* MPElementGeneratedEntity.m in Sources */,
DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */, DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */, DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */, DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
DA4426091557C1990052177D /* MPAppDelegate_Store.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; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -4011,9 +4193,9 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
DAFE4A1415039824003ABA7C /* NSObject_PearlExport.m in Sources */, DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */,
DAFE4A1615039824003ABA7C /* NSString_PearlNSArrayFormat.m in Sources */, DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */,
DAFE4A1815039824003ABA7C /* NSString_PearlSEL.m in Sources */, DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */,
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */, DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */,
DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */, DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */,
DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */, DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */,
@@ -4041,8 +4223,18 @@
DAFE4A5315039824003ABA7C /* PearlUIUtils.m in Sources */, DAFE4A5315039824003ABA7C /* PearlUIUtils.m in Sources */,
DAFE4A5515039824003ABA7C /* PearlValidatingTextField.m in Sources */, DAFE4A5515039824003ABA7C /* PearlValidatingTextField.m in Sources */,
DAFE4A5715039824003ABA7C /* PearlWebViewController.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 */, 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; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -4129,7 +4321,8 @@
CLANG_WARN_OBJCPP_ARC_ABI = YES; CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = 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_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -4169,7 +4362,7 @@
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}"; PRODUCT_NAME = "${TARGET_NAME}";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = "FCADE039-9917-4715-9172-ABBAC9806763";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@@ -4187,7 +4380,8 @@
CLANG_WARN_OBJCPP_ARC_ABI = YES; CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = 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_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
@@ -4226,10 +4420,11 @@
IPHONEOS_DEPLOYMENT_TARGET = 5.0; IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}"; PRODUCT_NAME = "${TARGET_NAME}";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = "1CC091A5-5890-4382-A72F-1397B66FE136";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
}; };
name = AdHoc; name = AdHoc;
}; };
@@ -4277,6 +4472,27 @@
}; };
name = AdHoc; 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 */ = { DA95D5A514DF063C008D1B94 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@@ -4310,7 +4526,8 @@
CLANG_WARN_OBJCPP_ARC_ABI = YES; CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = 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_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
@@ -4349,10 +4566,11 @@
IPHONEOS_DEPLOYMENT_TARGET = 5.0; IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}"; PRODUCT_NAME = "${TARGET_NAME}";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = "7BF77F10-5D46-4762-B27F-55A39E089052";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
}; };
name = AppStore; name = AppStore;
}; };
@@ -4569,6 +4787,16 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = AdHoc; 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" */ = { DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (

View File

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

View File

@@ -40,11 +40,11 @@
</MacroExpansion> </MacroExpansion>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug" buildConfiguration = "AppStore"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
@@ -57,12 +57,6 @@
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj"> ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.coredata.ubiquity.logLevel 3"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions> <AdditionalOptions>
<AdditionalOption <AdditionalOption
key = "NSZombieEnabled" key = "NSZombieEnabled"
@@ -75,7 +69,7 @@
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
buildConfiguration = "AdHoc" buildConfiguration = "Debug"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES">
<BuildableProductRunnable> <BuildableProductRunnable>
<BuildableReference <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) @interface MPAppDelegate_Shared (Key)
- (void)loadStoredKey; - (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
- (IBAction)signOut:(id)sender; - (void)signOut;
- (BOOL)tryMasterPassword:(NSString *)tryPassword; - (void)storeSavedKeyFor:(MPUserEntity *)user;
- (void)updateKey:(NSData *)key; - (void)forgetSavedKeyFor:(MPUserEntity *)user;
- (void)forgetKey;
- (NSData *)keyWithLength:(NSUInteger)keyLength;
@end @end

View File

@@ -6,153 +6,169 @@
// Copyright (c) 2011 Lyndir. All rights reserved. // Copyright (c) 2011 Lyndir. All rights reserved.
// //
#import "MPConfig.h" #import <Crashlytics/Crashlytics.h>
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPElementEntity.h" #import "MPAppDelegate_Store.h"
#import "LocalyticsSession.h"
@implementation MPAppDelegate_Shared (Key) @implementation MPAppDelegate_Shared (Key)
static NSDictionary *keyQuery() { static NSDictionary *keyQuery(MPUserEntity *user) {
static NSDictionary *MPKeyQuery = nil; return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
if (!MPKeyQuery) attributes:[NSDictionary dictionaryWithObjectsAndKeys:
MPKeyQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword @"Saved Master Password", (__bridge id)kSecAttrService,
attributes:[NSDictionary dictionaryWithObjectsAndKeys: user.name, (__bridge id)kSecAttrAccount,
@"Saved Master Password", (__bridge id)kSecAttrService, nil]
@"default", (__bridge id)kSecAttrAccount, matches:nil];
nil]
matches:nil];
return MPKeyQuery;
} }
static NSDictionary *keyIDQuery() { - (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
static NSDictionary *MPKeyIDQuery = nil; NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (!MPKeyIDQuery) if (key)
MPKeyIDQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword inf(@"Found key in keychain for: %@", user.userID);
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"Master Password Check", (__bridge id)kSecAttrService,
@"default", (__bridge id)kSecAttrAccount,
nil]
matches:nil];
return MPKeyIDQuery; else {
user.saveKey = NO;
inf(@"No key found in keychain for: %@", user.userID);
}
return key;
} }
- (void)forgetKey { - (void)storeSavedKeyFor:(MPUserEntity *)user {
inf(@"Deleting key and ID from keychain."); if (user.saveKey) {
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound) NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
inf(@"Removed key from keychain.");
if ([PearlKeyChain deleteItemForQuery:keyIDQuery()] != errSecItemNotFound)
inf(@"Removed key ID from keychain.");
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self]; if (![existingKey isEqualToData:self.key]) {
#ifdef TESTFLIGHT_SDK_VERSION inf(@"Saving key in keychain for: %@", user.userID);
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
#endif
}
- (IBAction)signOut:(id)sender { [PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
[MPConfig get].saveKey = [NSNumber numberWithBool:NO]; self.key, (__bridge id)kSecValueData,
[self updateKey:nil]; #if TARGET_OS_IPHONE
} kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
- (void)loadStoredKey { nil]];
}
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
} }
} }
- (BOOL)tryMasterPassword:(NSString *)tryPassword { - (void)forgetSavedKeyFor:(MPUserEntity *)user {
if (![tryPassword length]) OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
return NO; if (result == noErr || result == errSecItemNotFound) {
user.saveKey = NO;
NSData *tryKey = keyForPassword(tryPassword); if (result == noErr) {
NSData *tryKeyID = keyIDForKey(tryKey); inf(@"Removed key from keychain for: %@", user.userID);
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]);
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch]; [TestFlight passCheckpoint:MPCheckpointForgetSavedKey];
#endif #endif
return NO; }
}
}
- (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];
}
} }
#ifdef TESTFLIGHT_SDK_VERSION // Method 3: Check the given master password string.
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered]; if (!tryKey) {
#endif 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:MPCheckpointSignInFailed];
#endif
[[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:MPCheckpointSignedIn];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignedIn
attributes:nil];
[self updateKey:tryKey];
return YES; 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 @end

View File

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

View File

@@ -11,7 +11,7 @@
@implementation MPAppDelegate_Shared @implementation MPAppDelegate_Shared
@synthesize key; @synthesize key;
@synthesize keyID; @synthesize activeUser;
+ (MPAppDelegate_Shared *)get { + (MPAppDelegate_Shared *)get {

View File

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

View File

@@ -7,13 +7,10 @@
// //
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPElementEntity.h" #import "LocalyticsSession.h"
#import "MPConfig.h"
@implementation MPAppDelegate_Shared (Store) @implementation MPAppDelegate_Shared (Store)
static NSDateFormatter *rfc3339DateFormatter = nil;
#pragma mark - Core Data setup #pragma mark - Core Data setup
+ (NSManagedObjectContext *)managedObjectContext { + (NSManagedObjectContext *)managedObjectContext {
@@ -42,13 +39,10 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
if (managedObjectContext) if (managedObjectContext)
return managedObjectContext; return managedObjectContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
assert(coordinator);
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext performBlockAndWait:^{ [managedObjectContext performBlockAndWait:^{
managedObjectContext.persistentStoreCoordinator = coordinator; managedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
}]; }];
return managedObjectContext; return managedObjectContext;
@@ -56,13 +50,13 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// Wait until the storeManager is ready. // Start loading the store.
for(__block BOOL isReady = [self storeManager].isReady; !isReady;) [self storeManager];
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
isReady = [self storeManager].isReady; // Wait until the storeManager is ready.
}); while (![self storeManager].isReady)
[NSThread sleepForTimeInterval:0.1];
assert([self storeManager].isReady);
return [self storeManager].persistentStoreCoordinator; return [self storeManager].persistentStoreCoordinator;
} }
@@ -73,14 +67,15 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
return storeManager; return storeManager;
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel] storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"] localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared" containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSPersistentStoreFileProtectionKey] additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSPersistentStoreFileProtectionKey]
#else #else
additionalStoreOptions:nil additionalStoreOptions:nil
#endif #endif
]; ];
storeManager.delegate = self; storeManager.delegate = self;
#ifdef DEBUG #ifdef DEBUG
storeManager.hardResetEnabled = YES; storeManager.hardResetEnabled = YES;
@@ -88,9 +83,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication] queue:nil object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[storeManager checkiCloudStatus]; [storeManager checkiCloudStatus];
}]; }];
#else #else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
object:[NSApplication sharedApplication] queue:nil object:[NSApplication sharedApplication] queue:nil
@@ -101,9 +96,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
object:[UIApplication sharedApplication] queue:nil object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[self saveContext]; [self saveContext];
}]; }];
#else #else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
object:[NSApplication sharedApplication] queue:nil object:[NSApplication sharedApplication] queue:nil
@@ -121,52 +116,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
NSError *error = nil; NSError *error = nil;
if ([self.managedObjectContext hasChanges]) if ([self.managedObjectContext hasChanges])
if (![self.managedObjectContext save:&error]) if (![self.managedObjectContext save:&error])
err(@"While saving context: %@", 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.");
}]; }];
} }
@@ -184,23 +134,27 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - (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; iCloudEnabled = manager.iCloudEnabled;
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:iCloudEnabled? MPTestFlightCheckpointCloudEnabled: MPTestFlightCheckpointCloudDisabled]; [TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
#endif #endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled]; [MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
} }
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause context:(id)context { - (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 #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:PearlString(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)]; [TestFlight passCheckpoint:PearlString(@"MPCheckpointMPErrorUbiquity_%d", cause)];
#endif #endif
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
switch (cause) { switch (cause) {
case UbiquityStoreManagerErrorCauseDeleteStore: case UbiquityStoreManagerErrorCauseDeleteStore:
@@ -209,10 +163,13 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
case UbiquityStoreManagerErrorCauseClearStore: case UbiquityStoreManagerErrorCauseClearStore:
break; break;
case UbiquityStoreManagerErrorCauseOpenLocalStore: { case UbiquityStoreManagerErrorCauseOpenLocalStore: {
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointLocalStoreIncompatible];
#endif
wrn(@"Local store could not be opened, resetting it."); 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.hardResetEnabled = YES;
[manager hardResetLocalStorage]; [manager hardResetLocalStorage];
@@ -220,10 +177,13 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
return; return;
} }
case UbiquityStoreManagerErrorCauseOpenCloudStore: { case UbiquityStoreManagerErrorCauseOpenCloudStore: {
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointCloudStoreIncompatible];
#endif
wrn(@"iCloud store could not be opened, resetting it."); 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.hardResetEnabled = YES;
[manager hardResetCloudStorage]; [manager hardResetCloudStorage];
break; break;
@@ -233,49 +193,39 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
#pragma mark - Import / Export #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 - (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation { askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
[self loadRFC3339DateFormatter]; inf(@"Importing sites.");
static NSRegularExpression *headerPattern, *sitePattern; static NSRegularExpression *headerPattern, *sitePattern;
__autoreleasing NSError *error; __autoreleasing NSError *error;
if (!headerPattern) { if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc] headerPattern = [[NSRegularExpression alloc]
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)" initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:0 error:&error]; options:0 error:&error];
if (error) if (error)
err(@"Error loading the header pattern: %@", error); err(@"Error loading the header pattern: %@", error);
} }
if (!sitePattern) { if (!sitePattern) {
sitePattern = [[NSRegularExpression alloc] sitePattern = [[NSRegularExpression alloc]
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)" initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
options:0 error:&error]; options:0 error:&error];
if (error) if (error)
err(@"Error loading the site pattern: %@", error); err(@"Error loading the site pattern: %@", error);
} }
if (!headerPattern || !sitePattern) if (!headerPattern || !sitePattern)
return MPImportResultInternalError; return MPImportResultInternalError;
NSString *keyIDHex = nil; NSData *key = nil;
BOOL headerStarted = NO, headerEnded = NO; NSString *keyIDHex = nil, *userName = nil;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; MPUserEntity *user = nil;
NSMutableSet *elementsToDelete = [NSMutableSet set]; BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]]; NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
for(NSString *importedSiteLine in importedSiteLines) { for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) { if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header // Comment or header
if (!headerStarted) { if (!headerStarted) {
@@ -295,19 +245,32 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
err(@"Invalid header format in line: %@", importedSiteLine); err(@"Invalid header format in line: %@", importedSiteLine);
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
} }
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject]; NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]]; range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]]; NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
if ([key isEqualToString:@"Key ID"]) { NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]]) 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; return MPImportResultInvalidPassword;
} }
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
}
continue; continue;
} }
if (!headerEnded) if (!headerEnded)
continue; continue;
if (!keyIDHex) if (!keyIDHex || ![userName length])
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
if (![importedSiteLine length]) if (![importedSiteLine length])
continue; continue;
@@ -317,28 +280,38 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
err(@"Invalid site format in line: %@", importedSiteLine); err(@"Invalid site format in line: %@", importedSiteLine);
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
} }
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject]; NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
// Find existing site. // Find existing site.
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND keyID == %@", name, keyIDHex]; if (user) {
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
if (error) NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"Couldn't search existing sites: %@", error); err(@"Couldn't search existing sites: %@", error);
if (!existingSites) if (!existingSites) {
return MPImportResultInternalError; err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID);
return MPImportResultInternalError;
}
[elementsToDelete addObjectsFromArray:existingSites]; [elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]]; [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. // Ask for confirmation to import these sites.
if (!confirmation([importedSiteElements count], [elementsToDelete count])) if (!confirmation([importedSiteElements count], [elementsToDelete count])) {
inf(@"Import cancelled.");
return MPImportResultCancelled; return MPImportResultCancelled;
}
// Delete existing sites. // Delete existing sites.
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
@@ -348,37 +321,48 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[self saveContext]; [self saveContext];
// Import new sites. // 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) { for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]]; NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
NSInteger uses = [[siteElements objectAtIndex:1] integerValue]; NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
MPElementType type = (unsigned)[[siteElements objectAtIndex:2] integerValue]; MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
NSString *name = [siteElements objectAtIndex:3]; NSString *name = [siteElements objectAtIndex:3];
NSString *exportContent = [siteElements objectAtIndex:4]; NSString *exportContent = [siteElements objectAtIndex:4];
// Create new site. // Create new site.
inf(@"Importing site: name=%@, lastUsed=%@, uses=%d, type=%u, keyID=%@", name, lastUsed, uses, type, keyIDHex);
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:self.managedObjectContext]; inManagedObjectContext:self.managedObjectContext];
element.name = name; element.name = name;
element.keyID = [keyIDHex decodeHex]; element.user = user;
element.type = type; element.type = type;
element.uses = uses; element.uses = uses;
element.lastUsed = [lastUsed timeIntervalSinceReferenceDate]; element.lastUsed = lastUsed;
if ([exportContent length]) if ([exportContent length])
[element importContent:exportContent]; if (clearText)
[element importClearTextContent:exportContent usingKey:key];
else
[element importProtectedContent:exportContent];
} }
[self saveContext]; [self saveContext];
inf(@"Import completed successfully.");
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported]; [TestFlight passCheckpoint:MPCheckpointSitesImported];
#endif #endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported
attributes:nil];
return MPImportResultSuccess; return MPImportResultSuccess;
} }
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords { - (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
[self loadRFC3339DateFormatter]; inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", self.activeUser.userID);
// Header. // Header.
NSMutableString *export = [NSMutableString new]; NSMutableString *export = [NSMutableString new];
@@ -390,8 +374,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[export appendFormat:@"# \n"]; [export appendFormat:@"# \n"];
[export appendFormat:@"##\n"]; [export appendFormat:@"##\n"];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion]; [export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# Key ID: %@\n", [self.keyID encodeHex]]; [export appendFormat:@"# User Name: %@\n", self.activeUser.name];
[export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]]; [export appendFormat:@"# Key ID: %@\n", [self.activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
if (showPasswords) if (showPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"]; [export appendFormat:@"# Passwords: VISIBLE\n"];
else else
@@ -402,36 +387,31 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[export appendFormat:@"# used used type name\tpassword\n"]; [export appendFormat:@"# used used type name\tpassword\n"];
// Sites. // Sites.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; for (MPElementEntity *element in self.activeUser.elements) {
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]; NSDate *lastUsed = element.lastUsed;
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", self.keyID]; NSUInteger uses = element.uses;
__autoreleasing NSError *error = nil; MPElementType type = element.type;
NSArray *elements = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error]; NSString *name = element.name;
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;
NSString *content = nil; NSString *content = nil;
// Determine the content to export. // Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) { if (!(type & MPElementFeatureDevicePrivate)) {
if (showPasswords) if (showPasswords)
content = element.content; content = element.content;
else if (type & MPElementFeatureExportContent) else
content = element.exportContent; if (type & MPElementFeatureExportContent)
content = element.exportContent;
} }
[export appendFormat:@"%@ %8d %8d %20s\t%@\n", [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 #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported]; [TestFlight passCheckpoint:MPCheckpointSitesExported];
#endif #endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesExported attributes:nil];
return export; return export;
} }

View File

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

View File

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

View File

@@ -1,27 +1,23 @@
// //
// MPElementEntity.h // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <CoreData/CoreData.h> #import <CoreData/CoreData.h>
@class MPUserEntity;
@interface MPElementEntity : NSManagedObject @interface MPElementEntity : NSManagedObject
@property (nonatomic, retain) NSString *name; @property (nonatomic, retain) id content;
@property (nonatomic, retain) NSData *keyID; @property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, assign) int16_t type; @property (nonatomic, retain) NSString * name;
@property (nonatomic, assign) int16_t uses; @property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, assign) NSTimeInterval lastUsed; @property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) MPUserEntity *user;
@property (nonatomic, retain, readonly) id content;
- (int16_t)use;
- (NSString *)exportContent;
- (void)importContent:(NSString *)content;
@end @end

View File

@@ -1,51 +1,22 @@
// //
// MPElementEntity.m // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPElementEntity.h" #import "MPElementEntity.h"
#import "MPUserEntity.h"
@implementation MPElementEntity @implementation MPElementEntity
@dynamic name; @dynamic content;
@dynamic keyID;
@dynamic type;
@dynamic uses;
@dynamic lastUsed; @dynamic lastUsed;
@dynamic name;
- (int16_t)use { @dynamic type_;
@dynamic uses_;
self.lastUsed = [[NSDate date] timeIntervalSinceReferenceDate]; @dynamic user;
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);
}
@end @end

View File

@@ -1,8 +1,8 @@
// //
// MPElementGeneratedEntity.h // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity @interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, assign) int32_t counter; @property (nonatomic, retain) NSNumber * counter_;
@end @end

View File

@@ -1,31 +1,16 @@
// //
// MPElementGeneratedEntity.m // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPElementGeneratedEntity.h" #import "MPElementGeneratedEntity.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
@implementation MPElementGeneratedEntity @implementation MPElementGeneratedEntity
@dynamic counter; @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);
}
@end @end

View File

@@ -1,8 +1,8 @@
// //
// MPElementStoredEntity.h // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@@ -13,6 +13,6 @@
@interface MPElementStoredEntity : MPElementEntity @interface MPElementStoredEntity : MPElementEntity
@property (nonatomic, retain, readwrite) id content; @property (nonatomic, retain) id contentObject;
@end @end

View File

@@ -1,76 +1,16 @@
// //
// MPElementStoredEntity.m // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPElementStoredEntity.h" #import "MPElementStoredEntity.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
@interface MPElementStoredEntity ()
@property (nonatomic, retain, readwrite) id contentObject;
@end
@implementation MPElementStoredEntity @implementation MPElementStoredEntity
@dynamic contentObject; @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 @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 { typedef enum {
/** Generate the password. */ /** Generate the password. */
MPElementTypeClassGenerated = 1 << 4, MPElementTypeClassGenerated = 1 << 4,
/** Store the password. */ /** Store the password. */
MPElementTypeClassStored = 1 << 5, MPElementTypeClassStored = 1 << 5,
} MPElementTypeClass; } MPElementTypeClass;
typedef enum { typedef enum {
/** Export the key-protected content data. */ /** Export the key-protected content data. */
MPElementFeatureExportContent = 1 << 10, MPElementFeatureExportContent = 1 << 10,
/** Never export content. */ /** Never export content. */
MPElementFeatureDevicePrivate = 1 << 11, MPElementFeatureDevicePrivate = 1 << 11,
} MPElementFeature; } MPElementFeature;
typedef enum { typedef enum {
MPElementTypeGeneratedLong = 0x0 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x1 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x2 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedBasic = 0x3 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x4 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent, MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate, MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
} MPElementType; } MPElementType;
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction" #define MPCheckpointAction @"MPCheckpointAction"
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@" #define MPCheckpointHelpChapter @"MPCheckpointHelpChapter"
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard" #define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard"
#define MPTestFlightCheckpointResetPasswordCounter @"MPTestFlightCheckpointResetPasswordCounter" #define MPCheckpointResetPasswordCounter @"MPCheckpointResetPasswordCounter"
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter" #define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter"
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword" #define MPCheckpointEditPassword @"MPCheckpointEditPassword"
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert" #define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
#define MPTestFlightCheckpointSelectType @"MPTestFlightCheckpointSelectType_%@" #define MPCheckpointUseType @"MPCheckpointUseType"
#define MPTestFlightCheckpointSelectElement @"MPTestFlightCheckpointSelectElement" #define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
#define MPTestFlightCheckpointDeleteElement @"MPTestFlightCheckpointDeleteElement" #define MPCheckpointCancelSearch @"MPCheckpointCancelSearch"
#define MPTestFlightCheckpointCancelSearch @"MPTestFlightCheckpointCancelSearch" #define MPCheckpointExternalLink @"MPCheckpointExternalLink"
#define MPTestFlightCheckpointExternalLink @"MPTestFlightCheckpointExternalLink" #define MPCheckpointLaunched @"MPCheckpointLaunched"
#define MPTestFlightCheckpointLaunched @"MPTestFlightCheckpointLaunched" #define MPCheckpointActivated @"MPCheckpointActivated"
#define MPTestFlightCheckpointActivated @"MPTestFlightCheckpointActivated" #define MPCheckpointDeactivated @"MPCheckpointDeactivated"
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated" #define MPCheckpointTerminated @"MPCheckpointTerminated"
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated" #define MPCheckpointShowGuide @"MPCheckpointShowGuide"
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide" #define MPCheckpointForgetSavedKey @"MPCheckpointForgetSavedKey"
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten" #define MPCheckpointChangeMP @"MPCheckpointChangeMP"
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged" #define MPCheckpointLocalStoreIncompatible @"MPCheckpointLocalStoreIncompatible"
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored" #define MPCheckpointCloudStoreIncompatible @"MPCheckpointCloudStoreIncompatible"
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch" #define MPCheckpointSignInFailed @"MPCheckpointSignInFailed"
#define MPTestFlightCheckpointMPEntered @"MPTestFlightCheckpointMPEntered" #define MPCheckpointSignedIn @"MPCheckpointSignedIn"
#define MPTestFlightCheckpointLocalStoreIncompatible @"MPTestFlightCheckpointLocalStoreIncompatible" #define MPCheckpointConfig @"MPCheckpointConfig"
#define MPTestFlightCheckpointCloudStoreIncompatible @"MPTestFlightCheckpointCloudStoreIncompatible" #define MPCheckpointCloud @"MPCheckpointCloud"
#define MPTestFlightCheckpointSetKey @"MPTestFlightCheckpointSetKey" #define MPCheckpointCloudEnabled @"MPCheckpointCloudEnabled"
#define MPTestFlightCheckpointCloudEnabled @"MPTestFlightCheckpointCloudEnabled" #define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
#define MPTestFlightCheckpointCloudDisabled @"MPTestFlightCheckpointCloudDisabled" #define MPCheckpointSitesImported @"MPCheckpointSitesImported"
#define MPTestFlightCheckpointSitesImported @"MPTestFlightCheckpointSitesImported" #define MPCheckpointSitesExported @"MPCheckpointSitesExported"
#define MPTestFlightCheckpointSitesExported @"MPTestFlightCheckpointSitesExported"
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated" #define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
#define MPNotificationKeySet @"MPNotificationKeySet" #define MPNotificationSignedIn @"MPNotificationKeySet"
#define MPNotificationKeyUnset @"MPNotificationKeyUnset" #define MPNotificationSignedOut @"MPNotificationKeyUnset"
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten" #define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationElementUsed @"MPNotificationElementUsed"
NSData *keyForPassword(NSString *password); NSData *keyForPassword(NSString *password, NSString *username);
NSData *keyIDForPassword(NSString *password); NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
NSData *keyIDForKey(NSData *key); NSData *keyIDForPassword(NSString *password, NSString *username);
NSData *keyIDForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type); NSString *NSStringFromMPElementType(MPElementType type);
NSString *NSStringShortFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type); NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(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,40 +6,57 @@
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPTypes.h" #import "MPEntities.h"
#import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h"
#define MP_salt nil #define MP_N 131072
#define MP_N 16384
#define MP_r 8 #define MP_r 8
#define MP_p 1 #define MP_p 1
#define MP_dkLen 64 #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] NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p]; 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(@"Password: %@ derives to key ID: %@", password, [keyIDForKey(key) encodeHex]); trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2f)", username, password, [keyIDForKey(key) encodeHex], -[start timeIntervalSinceNow]);
return key; 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) { NSData *keyIDForKey(NSData *key) {
return [key hashWith:MP_hash]; return [key hashWith:MP_hash];
} }
NSString *NSStringFromMPElementType(MPElementType type) { NSString *NSStringFromMPElementType(MPElementType type) {
if (!type) if (!type)
return nil; return nil;
switch (type) { switch (type) {
case MPElementTypeGeneratedMaximum:
return @"Maximum Security Password";
case MPElementTypeGeneratedLong: case MPElementTypeGeneratedLong:
return @"Long Password"; return @"Long Password";
@@ -62,7 +79,42 @@ NSString *NSStringFromMPElementType(MPElementType type) {
return @"Device Private Password"; return @"Device Private Password";
default: 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);
} }
} }
@@ -72,6 +124,9 @@ Class ClassFromMPElementType(MPElementType type) {
return nil; return nil;
switch (type) { switch (type) {
case MPElementTypeGeneratedMaximum:
return [MPElementGeneratedEntity class];
case MPElementTypeGeneratedLong: case MPElementTypeGeneratedLong:
return [MPElementGeneratedEntity class]; return [MPElementGeneratedEntity class];
@@ -94,7 +149,7 @@ Class ClassFromMPElementType(MPElementType type) {
return [MPElementStoredEntity class]; return [MPElementStoredEntity class];
default: default:
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type]; Throw(@"Type not supported: %d", type);
} }
} }
@@ -104,46 +159,48 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
} }
static NSDictionary *MPTypes_ciphers = nil; 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)) { if (!(type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name); err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
return nil; return nil;
} }
if (!name) { if (!name.length) {
err(@"Missing name."); err(@"Missing name.");
return nil; return nil;
} }
if (!key) { if (!key.length) {
err(@"Key not set."); err(@"Missing key.");
return nil; return nil;
} }
uint32_t salt = (unsigned)counter;
if (!counter) if (!counter)
// Counter unset, go into OTP mode. // 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. // 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 = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
if (MPTypes_ciphers == nil) if (MPTypes_ciphers == nil)
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers" MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
withExtension:@"plist"]]; withExtension:@"plist"]];
// Determine the seed whose bytes will be used for calculating a password: sha1(name . '\0' . key . '\0' . salt) // Determine the seed whose bytes will be used for calculating a password
uint32_t nsalt = htonl(salt); trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter);
trc(@"seed from: sha1(%@, %@, %u)", name, key, nsalt); uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
NSData *seed = [[NSData dataByConcatenatingWithDelimitor:'\0' datas: NSData *seed = [[NSData dataByConcatenatingDatas:
[name dataUsingEncoding:NSUTF8StringEncoding], [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
key, [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)], [name dataUsingEncoding:NSUTF8StringEncoding],
nil] hashWith:PearlDigestSHA1]; [NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
nil]
hmacWith:PearlHashSHA256 key:key];
trc(@"seed is: %@", seed); trc(@"seed is: %@", seed);
const char *seedBytes = seed.bytes; const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte. // Determine the cipher from the first seed byte.
assert([seed length]); assert([seed length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)] NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
valueForKey:NSStringFromMPElementType(type)]; valueForKey:NSStringFromMPElementType(type)];
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]]; NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher); trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
// Encode the content, character by character, using subsequent seed bytes and the cipher. // Encode the content, character by character, using subsequent seed bytes and the cipher.
@@ -151,9 +208,10 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, in
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]]; NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
for (NSUInteger c = 0; c < [cipher length]; ++c) { for (NSUInteger c = 0; c < [cipher length]; ++c) {
uint16_t keyByte = htons(seedBytes[c + 1]); 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 *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); trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
[content appendString:character]; [content appendString:character];

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

View File

@@ -27,8 +27,10 @@
@synthesize key; @synthesize key;
@synthesize keyID; @synthesize keyID;
#pragma GCC diagnostic ignored "-Wfour-char-constants" #pragma clang diagnostic push
static EventHotKeyID MPShowHotKey = { .signature = 'show', .id = 1 }; #pragma clang diagnostic ignored "-Wfour-char-constants"
static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1};
#pragma clang diagnostic pop
+ (void)initialize { + (void)initialize {
@@ -44,12 +46,12 @@ static EventHotKeyID MPShowHotKey = { .signature = 'show', .id = 1 };
return (MPAppDelegate *)[super 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. // Extract the hotkey ID.
EventHotKeyID hotKeyID; EventHotKeyID hotKeyID;
GetEventParameter(theEvent,kEventParamDirectObject,typeEventHotKeyID, GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID,
NULL,sizeof(hotKeyID),NULL,&hotKeyID); NULL, sizeof(hotKeyID), NULL, &hotKeyID);
// Check which hotkey this was. // Check which hotkey this was.
if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) { if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) {
@@ -63,8 +65,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (void)showMenu { - (void)showMenu {
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState; self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState; self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
self.showItem.enabled = ![self.passwordWindow.window isVisible]; self.showItem.enabled = ![self.passwordWindow.window isVisible];
[self.statusItem popUpStatusItemMenu:self.statusMenu]; [self.statusItem popUpStatusItemMenu:self.statusMenu];
} }
@@ -84,7 +86,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
if (sender == rememberPasswordItem) if (sender == rememberPasswordItem)
[MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]]; [MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]];
if (sender == savePasswordItem) 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 { - (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
@@ -92,7 +94,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
if (configKey == @selector(rememberKey)) if (configKey == @selector(rememberKey))
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState; self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
if (configKey == @selector(saveKey)) 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 { - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
@@ -125,22 +127,23 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
[[self storeManager] useiCloudStore:YES alertUser:YES]; [[self storeManager] useiCloudStore:YES alertUser:YES];
// Status item. // Status item.
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
self.statusItem.title = @"•••"; self.statusItem.title = @"•••";
self.statusItem.highlightMode = YES; self.statusItem.highlightMode = YES;
self.statusItem.target = self; self.statusItem.target = self;
self.statusItem.action = @selector(showMenu); self.statusItem.action = @selector(showMenu);
// Global hotkey. // Global hotkey.
EventHotKeyRef hotKeyRef; EventHotKeyRef hotKeyRef;
EventTypeSpec hotKeyEvents[1] = { { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed } }; EventTypeSpec hotKeyEvents[1] = {{.eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed}};
OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents), hotKeyEvents, OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents),
(__bridge void *)self, NULL); hotKeyEvents,
if(status != noErr) (__bridge void *)self, NULL);
err(@"Error installing application event handler: %d", status); if (status != noErr)
status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef); err(@"Error installing application event handler: %d", status);
if(status != noErr) status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef);
err(@"Error registering hotkey: %d", status); if (status != noErr)
err(@"Error registering hotkey: %d", status);
} }
- (void)applicationWillBecomeActive:(NSNotification *)notification { - (void)applicationWillBecomeActive:(NSNotification *)notification {
@@ -164,8 +167,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
self.key = nil; self.key = nil;
} }
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
{
// Save changes in the application's managed object context before the application terminates. // Save changes in the application's managed object context before the application terminates.
if (![self managedObjectContext]) { if (![self managedObjectContext]) {
@@ -190,11 +192,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
return NSTerminateCancel; return NSTerminateCancel;
} }
NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message"); 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 *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 *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title"); NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title");
NSAlert *alert = [[NSAlert alloc] init]; NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:question]; [alert setMessageText:question];
[alert setInformativeText:info]; [alert setInformativeText:info];
[alert addButtonWithTitle:quitButton]; [alert addButtonWithTitle:quitButton];
@@ -214,7 +216,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState; self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState;
self.useICloudItem.enabled = !iCloudEnabled; self.useICloudItem.enabled = !iCloudEnabled;
} }

View File

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

View File

@@ -59,18 +59,18 @@
- (void)unlock { - (void)unlock {
if (![MPAppDelegate get].key) if (![MPAppDelegate get].key)
// Try and load the key from the keychain. // Try and load the key from the keychain.
[[MPAppDelegate get] loadStoredKey]; [[MPAppDelegate get] loadStoredKey];
if (![MPAppDelegate get].key) 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(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if ([MPAppDelegate get].key) if ([MPAppDelegate get].key)
return; return;
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked." NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit" defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
informativeTextWithFormat:@"Your master password is required to unlock the application."]; informativeTextWithFormat:@"Your master password is required to unlock the application."];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)]; NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)];
[alert setAccessoryView:passwordField]; [alert setAccessoryView:passwordField];
[alert layout]; [alert layout];
@@ -80,19 +80,20 @@
}); });
} }
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
switch (returnCode) { switch (returnCode) {
case NSAlertAlternateReturn: case NSAlertAlternateReturn:
// "Change" button. // "Change" button.
if ([[NSAlert alertWithMessageText:@"Changing Master Password" if ([[NSAlert alertWithMessageText:@"Changing Master Password"
defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil
informativeTextWithFormat: informativeTextWithFormat:
@"This will allow you to log in with a different master password.\n\n" @"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" @"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" @"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" @"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."] runModal] == 1) @"Your current sites and passwords will then become available again."] runModal]
== 1)
[[MPAppDelegate get] forgetKey]; [[MPAppDelegate get] forgetKey];
break; break;
@@ -107,23 +108,24 @@
} }
} }
- (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]; NSString *query = [[control stringValue] substringWithRange:charRange];
if (![query length] || ![MPAppDelegate get].keyID) if (![query length] || ![MPAppDelegate get].keyID)
return nil; return nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND keyID == %@", fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
query, query, [MPAppDelegate get].keyID]; query, query, [MPAppDelegate get].activeUser];
NSError *error = nil; NSError *error = nil;
self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error]; self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (error) if (error)
err(@"Couldn't fetch elements: %@", error); err(@"Couldn't fetch elements: %@", error);
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1]; NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
if (self.siteResults) if (self.siteResults)
for (MPElementEntity *element in self.siteResults) for (MPElementEntity *element in self.siteResults)
[mutableResults addObject:element.name]; [mutableResults addObject:element.name];
@@ -153,7 +155,7 @@
[[self findElement] use]; [[self findElement] use];
return YES; return YES;
} else } else
wrn(@"Couldn't copy password to pasteboard."); wrn(@"Couldn't copy password to pasteboard.");
} }
return NO; return NO;
@@ -175,19 +177,19 @@
_content = content; _content = content;
NSShadow *shadow = [NSShadow new]; NSShadow *shadow = [NSShadow new];
shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f]; shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f];
shadow.shadowOffset = NSMakeSize(1.0f, -1.0f); shadow.shadowOffset = NSMakeSize(1.0f, -1.0f);
shadow.shadowBlurRadius = 1.2f; shadow.shadowBlurRadius = 1.2f;
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new]; NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
paragraph.alignment = NSCenterTextAlignment; paragraph.alignment = NSCenterTextAlignment;
[self.contentField setAttributedStringValue: [self.contentField setAttributedStringValue:
[[NSAttributedString alloc] initWithString:_content [[NSAttributedString alloc] initWithString:_content
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys: attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
shadow, NSShadowAttributeName, shadow, NSShadowAttributeName,
paragraph, NSParagraphStyleAttributeName, paragraph, NSParagraphStyleAttributeName,
nil]]]; nil]]];
} }
- (BOOL)trySite { - (BOOL)trySite {

View File

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

View File

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

View File

@@ -1,21 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic"> <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"> <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="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/> <attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="16" syncable="YES"/> <attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" 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>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES"> <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>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES"> <entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/> <attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity> </entity>
<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> <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="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPElementStoredEntity" 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> </elements>
</model> </model>

View File

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

View File

@@ -10,21 +10,18 @@
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPMainViewController.h"
#import "IASKSettingsReader.h" #import "IASKSettingsReader.h"
#import "LocalyticsSession.h" #import "LocalyticsSession.h"
#import "TestFlight.h"
#import <Crashlytics/Crashlytics.h>
@interface MPAppDelegate () @interface MPAppDelegate ()
- (NSString *)testFlightInfo; - (NSDictionary *)testFlightInfo;
- (NSString *)testFlightToken; - (NSString *)testFlightToken;
- (NSString *)crashlyticsInfo; - (NSDictionary *)crashlyticsInfo;
- (NSString *)crashlyticsAPIKey; - (NSString *)crashlyticsAPIKey;
- (NSString *)localyticsInfo; - (NSDictionary *)localyticsInfo;
- (NSString *)localyticsKey; - (NSString *)localyticsKey;
@end @end
@@ -47,189 +44,116 @@
return (MPAppDelegate *)[super 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 { - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#ifndef DEBUG [[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ [[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
@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; @try {
}]; NSString *testFlightToken = [self testFlightToken];
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched]; if ([testFlightToken length]) {
} inf(@"Initializing TestFlight");
} [TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
@catch (NSException *exception) { #ifdef ADHOC
err(@"TestFlight: %@", exception); [TestFlight setDeviceIdentifier:[(id)[UIDevice currentDevice] uniqueIdentifier]];
} #else
@try { [TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
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);
}
});
#endif #endif
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], @"logToConsole",
[NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight takeOff:testFlightToken];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPiOSConfig get].sendInfo boolValue])
level = PearlLogLevelInfo;
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)]; 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:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone]; [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone];
[[UINavigationBar appearance] setTitleTextAttributes: [[UINavigationBar appearance] setTitleTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys: [NSDictionary dictionaryWithObjectsAndKeys:
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor, [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, [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset, [NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont, [UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont,
nil]]; nil]];
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)]; 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)]; UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
[[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; [[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; [[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setTitleTextAttributes: [[UIBarButtonItem appearance] setTitleTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys: [NSDictionary dictionaryWithObjectsAndKeys:
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor, [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, [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset, [NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont, [UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
nil] nil]
forState:UIControlStateNormal]; forState:UIControlStateNormal];
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)]; UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
[[UISearchBar appearance] setBackgroundImage:toolBarImage]; [[UISearchBar appearance] setBackgroundImage:toolBarImage];
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault]; [[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
@@ -253,8 +177,13 @@
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal 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: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 [[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[self checkConfig]; [self checkConfig];
@@ -268,57 +197,65 @@
@"lhunath@lyndir.com\n" @"lhunath@lyndir.com\n"
@"Or report detailed issues at:\n" @"Or report detailed issues at:\n"
@"https://youtrack.lyndir.com\n" @"https://youtrack.lyndir.com\n"
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil]; cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#endif #endif
[[UIApplication sharedApplication] setStatusBarHidden:NO [super application:application didFinishLaunchingWithOptions:launchOptions];
withAnimation:UIStatusBarAnimationSlide];
return [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 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
__autoreleasing NSError *error; __autoreleasing NSError *error;
__autoreleasing NSURLResponse *response; __autoreleasing NSURLResponse *response;
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error]; returningResponse:&response error:&error];
if (error) if (error)
err(@"While reading imported sites from %@: %@", url, error); err(@"While reading imported sites from %@: %@", url, error);
if (!importedSitesData) if (!importedSitesData)
return NO; return NO;
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
[PearlAlert showAlertWithTitle:@"Import Password" message: [PearlAlert showAlertWithTitle:@"Import Password" message:
@"Enter the master password for this export:" @"Enter the master password for this export:"
viewStyle:UIAlertViewStyleSecureTextInput tappedButtonBlock: viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) { ^(UIAlertView *alert, NSInteger buttonIndex) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) { askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
__block BOOL confirmation = NO; __block BOOL confirmation = NO;
dispatch_group_t confirmationGroup = dispatch_group_create(); dispatch_group_t confirmationGroup = dispatch_group_create();
dispatch_group_enter(confirmationGroup); dispatch_group_enter(confirmationGroup);
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import Sites?" [PearlAlert showAlertWithTitle:@"Import Sites?"
message:PearlLocalize(@"Import %d sites, overwriting %d existing sites?", importCount, deleteCount) message:PearlString(
viewStyle:UIAlertViewStyleDefault @"Import %d sites, overwriting %d existing sites?",
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { importCount, deleteCount)
if (buttonIndex != [alert cancelButtonIndex]) viewStyle:UIAlertViewStyleDefault
confirmation = YES; initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_
!= [alert_ cancelButtonIndex])
confirmation = YES;
dispatch_group_leave(confirmationGroup); dispatch_group_leave(confirmationGroup);
} }
cancelTitle:[PearlStrings get].commonButtonCancel cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:@"Import", nil]; otherTitles:@"Import", nil];
}); });
dispatch_group_wait(confirmationGroup, DISPATCH_TIME_FOREVER); dispatch_group_wait(
confirmationGroup, DISPATCH_TIME_FOREVER);
return confirmation; return confirmation;
}]; }];
switch (result) { switch (result) {
case MPImportResultSuccess: case MPImportResultSuccess:
@@ -336,21 +273,22 @@
} }
}); });
} }
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil]; cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
return YES; return YES;
} }
- (void)applicationDidBecomeActive:(UIApplication *)application { - (void)applicationDidBecomeActive:(UIApplication *)application {
inf(@"Re-activated");
[[MPAppDelegate get] checkConfig];
if ([[MPiOSConfig get].showQuickStart boolValue]) if ([[MPiOSConfig get].showQuickStart boolValue])
[self showGuide]; [self showGuide];
else {
[self loadKey:NO];
[self checkConfig];
}
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated]; [TestFlight passCheckpoint:MPCheckpointActivated];
[super applicationDidBecomeActive:application];
} }
- (void)applicationDidEnterBackground:(UIApplication *)application { - (void)applicationDidEnterBackground:(UIApplication *)application {
@@ -373,7 +311,7 @@
[self saveContext]; [self saveContext];
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated]; [TestFlight passCheckpoint:MPCheckpointTerminated];
[[LocalyticsSession sharedLocalyticsSession] close]; [[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload]; [[LocalyticsSession sharedLocalyticsSession] upload];
@@ -383,14 +321,187 @@
- (void)applicationWillResignActive:(UIApplication *)application { - (void)applicationWillResignActive:(UIApplication *)application {
inf(@"Will deactivate");
[self saveContext]; [self saveContext];
if (![[MPiOSConfig get].rememberKey boolValue]) { if (![[MPiOSConfig get].rememberLogin boolValue])
[self updateKey:nil]; [self signOut];
[self loadKey:NO];
[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 {
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;
} }
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated]; 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 #pragma mark - MFMailComposeViewControllerDelegate
@@ -399,7 +510,7 @@
didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
if (error) if (error)
err(@"Error composing mail message: %@", error); err(@"Error composing mail message: %@", error);
switch (result) { switch (result) {
case MFMailComposeResultSaved: case MFMailComposeResultSaved:
@@ -407,10 +518,11 @@
break; break;
case MFMailComposeResultFailed: case MFMailComposeResultFailed:
[PearlAlert showError:@"A problem occurred while sending the message." tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { [PearlAlert showError:@"A problem occurred while sending the message."
if (buttonIndex == [alert firstOtherButtonIndex]) tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
return; if (buttonIndex == [alert firstOtherButtonIndex])
} otherTitles:@"Retry", nil]; return;
} otherTitles:@"Retry", nil];
return; return;
case MFMailComposeResultCancelled: case MFMailComposeResultCancelled:
break; break;
@@ -429,37 +541,38 @@
if (!iCloudEnabled) { if (!iCloudEnabled) {
[PearlAlert showAlertWithTitle:@"iCloud" [PearlAlert showAlertWithTitle:@"iCloud"
message: message:
@"iCloud is now disabled.\n\n" @"iCloud is now disabled.\n\n"
@"It is highly recommended you enable iCloud." @"It is highly recommended you enable iCloud."
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { viewStyle:UIAlertViewStyleDefault initAlert:nil
if (buttonIndex == [alert firstOtherButtonIndex] + 0) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[PearlAlert showAlertWithTitle:@"About iCloud" if (buttonIndex == [alert firstOtherButtonIndex] + 0) {
message: [PearlAlert showAlertWithTitle:@"About iCloud"
@"iCloud is Apple's solution for saving your data in \"the cloud\" " message:
@"and making sure your other iPhones, iPads and Macs are in sync.\n\n" @"iCloud is Apple's solution for saving your data in \"the cloud\" "
@"For Master Password, that means your sites are available on all your " @"and making sure your other iPhones, iPads and Macs are in sync.\n\n"
@"Apple devices, and you always have a backup of them in case " @"For Master Password, that means your sites are available on all your "
@"you loose one or need to restore.\n\n" @"Apple devices, and you always have a backup of them in case "
@"Because of the way Master Password works, it doesn't need to send your " @"you loose one or need to restore.\n\n"
@"site's passwords to Apple. Only their names are saved to make it easier " @"Because of the way Master Password works, it doesn't need to send your "
@"for you to find the site you need. For some sites you may have set " @"site's passwords to Apple. Only their names are saved to make it easier "
@"a user-specified password: these are sent to iCloud after being encrypted " @"for you to find the site you need. For some sites you may have set "
@"with your master password.\n\n" @"a user-specified password: these are sent to iCloud after being encrypted "
@"Apple can never see any of your passwords." @"with your master password.\n\n"
viewStyle:UIAlertViewStyleDefault @"Apple can never see any of your passwords."
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { viewStyle:UIAlertViewStyleDefault
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
} [self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil]; }
return; cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
} return;
}
[MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES]; [MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES];
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
if (buttonIndex == [alert firstOtherButtonIndex] + 1) if (buttonIndex == [alert firstOtherButtonIndex] + 1)
[manager useiCloudStore:YES alertUser:NO]; [manager useiCloudStore:YES alertUser:NO];
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil]; } cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
} }
} }
} }
@@ -472,14 +585,14 @@
static NSDictionary *testFlightInfo = nil; static NSDictionary *testFlightInfo = nil;
if (testFlightInfo == nil) if (testFlightInfo == nil)
testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL: testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]]; [[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
return testFlightInfo; return testFlightInfo;
} }
- (NSString *)testFlightToken { - (NSString *)testFlightToken {
return NullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]); return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
} }
@@ -491,14 +604,14 @@
static NSDictionary *crashlyticsInfo = nil; static NSDictionary *crashlyticsInfo = nil;
if (crashlyticsInfo == nil) if (crashlyticsInfo == nil)
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL: crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]]; [[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
return crashlyticsInfo; return crashlyticsInfo;
} }
- (NSString *)crashlyticsAPIKey { - (NSString *)crashlyticsAPIKey {
return NullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]); return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
} }
@@ -510,7 +623,7 @@
static NSDictionary *localyticsInfo = nil; static NSDictionary *localyticsInfo = nil;
if (localyticsInfo == nil) if (localyticsInfo == nil)
localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL: localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]]; [[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
return localyticsInfo; return localyticsInfo;
} }
@@ -518,11 +631,9 @@
- (NSString *)localyticsKey { - (NSString *)localyticsKey {
#ifdef DEBUG #ifdef DEBUG
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]); return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
#elif defined(LITE)
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution.lite"]);
#else #else
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]); return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
#endif #endif
} }

View File

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

View File

@@ -7,11 +7,11 @@
// //
#import "MPGuideViewController.h" #import "MPGuideViewController.h"
#import "MPAppDelegate.h"
@implementation MPGuideViewController @implementation MPGuideViewController
@synthesize scrollView; @synthesize scrollView;
@synthesize pageControl;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
@@ -22,27 +22,34 @@
[super 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 { - (void)viewWillDisappear:(BOOL)animated {
inf(@"Guide will disappear.");
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO]; [MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
} }
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[MPAppDelegate get] loadKey:animated];
}
- (void)viewDidUnload { - (void)viewDidUnload {
[self setScrollView:nil]; [self setScrollView:nil];
[self setPageControl:nil];
[super viewDidUnload]; [super viewDidUnload];
} }
@@ -51,4 +58,12 @@
[self.presentingViewController dismissModalViewControllerAnimated:YES]; [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 @end

View File

@@ -6,31 +6,34 @@
// Copyright (c) 2011 Lyndir. All rights reserved. // Copyright (c) 2011 Lyndir. All rights reserved.
// //
#import <MessageUI/MessageUI.h>
#import "MPTypeViewController.h" #import "MPTypeViewController.h"
#import "MPElementEntity.h" #import "MPElementEntity.h"
#import "MPSearchDelegate.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 (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
@property (weak, nonatomic) IBOutlet UITextField *contentField; @property (weak, nonatomic) IBOutlet UITextField *contentField;
@property (weak, nonatomic) IBOutlet UIButton *typeButton; @property (weak, nonatomic) IBOutlet UIButton *typeButton;
@property (weak, nonatomic) IBOutlet UIWebView *helpView; @property (weak, nonatomic) IBOutlet UIWebView *helpView;
@property (weak, nonatomic) IBOutlet UILabel *siteName; @property (weak, nonatomic) IBOutlet UILabel *siteName;
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter; @property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer; @property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit; @property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
@property (weak, nonatomic) IBOutlet UIView *contentContainer; @property (weak, nonatomic) IBOutlet UIView *contentContainer;
@property (weak, nonatomic) IBOutlet UIView *helpContainer; @property (weak, nonatomic) IBOutlet UIView *helpContainer;
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer; @property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
@property (weak, nonatomic) IBOutlet UIView *alertContainer; @property (weak, nonatomic) IBOutlet UIView *alertContainer;
@property (weak, nonatomic) IBOutlet UILabel *alertTitle; @property (weak, nonatomic) IBOutlet UILabel *alertTitle;
@property (weak, nonatomic) IBOutlet UITextView *alertBody; @property (weak, nonatomic) IBOutlet UITextView *alertBody;
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody; @property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon; @property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer; @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); @property (copy) void (^contentTipCleanup)(BOOL finished);

View File

@@ -10,17 +10,12 @@
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPElementGeneratedEntity.h" #import "LocalyticsSession.h"
#import "MPElementStoredEntity.h"
#import "IASKAppSettingsViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
@interface MPMainViewController (Private) @interface MPMainViewController (Private)
- (void)updateAnimated:(BOOL)animated; - (void)updateAnimated:(BOOL)animated;
- (void)updateWasAnimated:(BOOL)animated;
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon; - (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message; - (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; - (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task;
@@ -45,7 +40,10 @@
@synthesize alertBody = _alertBody; @synthesize alertBody = _alertBody;
@synthesize contentTipBody = _contentTipBody; @synthesize contentTipBody = _contentTipBody;
@synthesize contentTipEditIcon = _contentTipEditIcon; @synthesize contentTipEditIcon = _contentTipEditIcon;
@synthesize searchTipContainer = _searchTip; @synthesize searchTipContainer = _searchTipContainer;
@synthesize actionsTipContainer = _actionsTipContainer;
@synthesize typeTipContainer = _typeTipContainer;
@synthesize resetPasswordCounterGesture = _resetPasswordCounterGesture;
@synthesize contentField = _contentField; @synthesize contentField = _contentField;
@synthesize contentTipCleanup; @synthesize contentTipCleanup;
@@ -53,7 +51,7 @@
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad || interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown; return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
} }
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
@@ -64,63 +62,76 @@
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - (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; ((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 { - (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.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize]; 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. self.alertBody.text = nil;
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.contentTipEditIcon.hidden = YES; self.contentTipEditIcon.hidden = YES;
[super viewDidLoad]; [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 { - (void)viewDidUnload {
[self setContentField:nil]; [self setContentField:nil];
@@ -140,40 +151,36 @@
[self setContentTipBody:nil]; [self setContentTipBody:nil];
[self setContentTipEditIcon:nil]; [self setContentTipEditIcon:nil];
[self setSearchTipContainer:nil]; [self setSearchTipContainer:nil];
[self setActionsTipContainer:nil];
[self setTypeTipContainer:nil];
[self setResetPasswordCounterGesture:nil];
[super viewDidUnload]; [super viewDidUnload];
} }
- (void)updateAnimated:(BOOL)animated { - (void)updateAnimated:(BOOL)animated {
[[MPAppDelegate get] saveContext]; if (animated) {
[UIView animateWithDuration:0.3f animations:^{
dispatch_async(dispatch_get_main_queue(), ^{ [self updateAnimated:NO];
if (animated) }];
[UIView animateWithDuration:0.3f animations:^{ return;
[self updateWasAnimated:animated]; }
}];
else
[self updateWasAnimated:animated];
});
}
- (void)updateWasAnimated:(BOOL)animated {
[self setHelpChapter:self.activeElement? @"2": @"1"]; [self setHelpChapter:self.activeElement? @"2": @"1"];
self.siteName.text = self.activeElement.name; 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.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0; self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
[self.typeButton setTitle:NSStringFromMPElementType((unsigned)self.activeElement.type) [self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
forState:UIControlStateNormal]; 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; self.contentField.enabled = NO;
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) 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 = @""; self.contentField.text = @"";
if (self.activeElement.name && ![self.activeElement isDeleted]) if (self.activeElement.name && ![self.activeElement isDeleted])
@@ -201,12 +208,12 @@
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{ [UIView animateWithDuration:animated? 0.3f: 0 animations:^{
if (hidden) { if (hidden) {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44); 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.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES]; [MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES];
} else { } else {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175); self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216); self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO]; [MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO];
} }
}]; }];
@@ -215,22 +222,21 @@
- (void)setHelpChapter:(NSString *)chapter { - (void)setHelpChapter:(NSString *)chapter {
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]]; [TestFlight passCheckpoint:PearlString(MPCheckpointHelpChapter @"_%@", chapter)];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.helpView loadRequest: NSURL *url = [NSURL URLWithString:[@"#" stringByAppendingString:chapter]
[NSURLRequest requestWithURL: relativeToURL:[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]];
[NSURL URLWithString:[NSString stringWithFormat:@"#%@", chapter] relativeToURL: [self.helpView loadRequest:[NSURLRequest requestWithURL:url]];
[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]];
}); });
} }
- (void)webViewDidFinishLoad:(UIWebView *)webView { - (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');", NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
ClassNameFromMPElementType((unsigned)self.activeElement.type)]]; PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
if (error.length) if (error.length)
err(@"helpView.setClass: %@", error); err(@"helpView.setClass: %@", error);
} }
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon { - (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
@@ -240,21 +246,21 @@
self.contentTipCleanup(NO); self.contentTipCleanup(NO);
self.contentTipBody.text = message; self.contentTipBody.text = message;
self.contentTipCleanup = ^(BOOL finished) { self.contentTipCleanup = ^(BOOL finished) {
icon.hidden = YES; icon.hidden = YES;
self.contentTipCleanup = nil; self.contentTipCleanup = nil;
}; };
icon.hidden = NO; icon.hidden = NO;
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.3f animations:^{
self.contentTipContainer.alpha = 1; self.contentTipContainer.alpha = 1;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) { if (finished) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC); 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:^{ [UIView animateWithDuration:0.2f animations:^{
self.contentTipContainer.alpha = 0; self.contentTipContainer.alpha = 0;
} completion:self.contentTipCleanup]; } completion:self.contentTipCleanup];
}); });
} }
}]; }];
@@ -272,7 +278,7 @@
self.alertBody.text = message; self.alertBody.text = message;
[self.alertBody scrollRangeToVisible:scrollRange]; [self.alertBody scrollRangeToVisible:scrollRange];
[UIView animateWithDuration:0.2f animations:^{ [UIView animateWithDuration:0.3f animations:^{
self.alertContainer.alpha = 1; self.alertContainer.alpha = 1;
}]; }];
}); });
@@ -285,64 +291,78 @@
if (!self.activeElement) if (!self.activeElement)
return; return;
[[UIPasteboard generalPasteboard] setValue:self.activeElement.content inf(@"Copying password for: %@", self.activeElement.name);
forPasteboardType:(id)kUTTypeUTF8PlainText]; [UIPasteboard generalPasteboard].string = [self.activeElement.content description];
[self showContentTip:@"Copied!" withIcon:nil]; [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 { - (IBAction)incrementPasswordCounter {
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) 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; return;
[self changeElementWithWarning: [self changeElementWithWarning:
@"You are incrementing the site's password counter.\n\n" @"You are incrementing the site's password counter.\n\n"
@"If you continue, a new password will be generated for this site. " @"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 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 can reset the counter by holding down on this button."
do:^{ do:^{
++((MPElementGeneratedEntity *) self.activeElement).counter; inf(@"Incrementing password counter for: %@", self.activeElement.name);
}]; ++((MPElementGeneratedEntity *)self.activeElement).counter;
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter]; [TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
}];
} }
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender { - (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) if (sender.state != UIGestureRecognizerStateBegan)
// Only fire when the gesture was first detected. // Only fire when the gesture was first detected.
return; return;
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) 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; return;
if (((MPElementGeneratedEntity *)self.activeElement).counter == 1) if (((MPElementGeneratedEntity *)self.activeElement).counter == 1)
// Counter has initial value, no point resetting. // Counter has initial value, no point resetting.
return; return;
[self changeElementWithWarning: [self changeElementWithWarning:
@"You are resetting the site's password counter.\n\n" @"You are resetting the site's password counter.\n\n"
@"If you continue, the site's password will change back to its original value. " @"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 will then need to update your account's password back to this original value."
do:^{ do:^{
((MPElementGeneratedEntity *) self.activeElement).counter = 1; inf(@"Resetting password counter for: %@", self.activeElement.name);
}]; ((MPElementGeneratedEntity *)self.activeElement).counter = 1;
[TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter]; [TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
nil]];
}];
} }
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; { - (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault [PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
[self changeElementWithoutWarningDo:task]; [self changeElementWithoutWarningDo:task];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
} }
- (void)changeElementWithoutWarningDo:(void (^)(void))task; { - (void)changeElementWithoutWarningDo:(void (^)(void))task; {
@@ -351,15 +371,17 @@
NSString *oldPassword = [self.activeElement.content description]; NSString *oldPassword = [self.activeElement.content description];
task(); task();
NSString *newPassword = [self.activeElement.content description]; NSString *newPassword = [self.activeElement.content description];
[[MPAppDelegate get] saveContext];
[self updateAnimated:YES]; [self updateAnimated:YES];
// Show new and old password. // Show new and old password.
if ([oldPassword length] && ![oldPassword isEqualToString:newPassword]) if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
[self showAlertWithTitle:@"Password Changed!" message:PearlLocalize(@"The password for %@ has changed.\n\n" [self showAlertWithTitle:@"Password Changed!"
@"IMPORTANT:\n" message:PearlString(@"The password for %@ has changed.\n\n"
@"Don't forget to update the site with your new password! " @"IMPORTANT:\n"
@"Your old password was:\n" @"Don't forget to update the site with your new password! "
@"%@", self.activeElement.name, oldPassword)]; @"Your old password was:\n"
@"%@", self.activeElement.name, oldPassword)];
} }
@@ -368,21 +390,26 @@
if (self.activeElement.type & MPElementTypeClassStored) { if (self.activeElement.type & MPElementTypeClassStored) {
self.contentField.enabled = YES; self.contentField.enabled = YES;
[self.contentField becomeFirstResponder]; [self.contentField becomeFirstResponder];
}
[TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword]; [TestFlight passCheckpoint:MPCheckpointEditPassword];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
nil]];
}
} }
- (IBAction)closeAlert { - (IBAction)closeAlert {
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
self.alertContainer.alpha = 0; self.alertContainer.alpha = 0;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) if (finished)
self.alertBody.text = nil; self.alertBody.text = nil;
}]; }];
[TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert]; [TestFlight passCheckpoint:MPCheckpointCloseAlert];
} }
- (IBAction)action:(id)sender { - (IBAction)action:(id)sender {
@@ -393,74 +420,133 @@
return; return;
switch (buttonIndex - [sheet firstOtherButtonIndex]) { switch (buttonIndex - [sheet firstOtherButtonIndex]) {
case 0: case 0: {
inf(@"Action: Toggle Help");
[self toggleHelpAnimated:YES]; [self toggleHelpAnimated:YES];
break; break;
}
case 1: { case 1: {
inf(@"Action: FAQ");
[self setHelpChapter:@"faq"]; [self setHelpChapter:@"faq"];
[self setHelpHidden:NO animated:YES]; [self setHelpHidden:NO animated:YES];
break; break;
} }
case 2: case 2: {
inf(@"Action: Guide");
[[MPAppDelegate get] showGuide]; [[MPAppDelegate get] showGuide];
break; break;
case 3: }
{ case 3: {
IASKAppSettingsViewController *settingsVC = [IASKAppSettingsViewController new]; inf(@"Action: Preferences");
settingsVC.delegate = self; [self performSegueWithIdentifier:@"UserProfile" sender:self];
[self.navigationController pushViewController:settingsVC animated:YES];
break; break;
} }
case 4:
[[MPAppDelegate get] export];
break;
#ifdef ADHOC #ifdef ADHOC
case 5: case 4: {
inf(@"Action: Feedback via TestFlight");
[TestFlight openFeedbackView]; [TestFlight openFeedbackView];
break; break;
case 6: }
case 5:
#else #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: case 5:
#endif #endif
{ {
[[MPAppDelegate get] signOut:self]; inf(@"Action: Sign out");
[[MPAppDelegate get] loadKey:YES]; [[MPAppDelegate get] signOut];
break; break;
} }
} }
[TestFlight passCheckpoint:MPTestFlightCheckpointAction]; [TestFlight passCheckpoint:MPCheckpointAction];
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil }
otherTitles: cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings", @"Export", [self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
#ifdef ADHOC }
@"Feedback",
#endif - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result
@"Sign Out", error:(NSError *)error {
nil];
if (error)
err(@"Feedback composer error: %@, result: %d", error, result);
else
inf(@"Feedback composer result: %d", result);
[controller dismissViewControllerAnimated:YES completion:nil];
} }
- (MPElementType)selectedType { - (MPElementType)selectedType {
return (unsigned)self.activeElement.type; return self.activeElement.type;
} }
- (void)didSelectType:(MPElementType)type { - (void)didSelectType:(MPElementType)type {
[self changeElementWithWarning: [self changeElementWithWarning:
@"You are about to change the type of this password.\n\n" @"You are about to change the type of this password.\n\n"
@"If you continue, the password for this site will change. " @"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 will need to update your account's old password to the new one."
do:^{ do:^{
// Update password type. // Update password type.
if (ClassFromMPElementType(type) != ClassFromMPElementType((unsigned)self.activeElement.type)) if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
// Type requires a different class of element. Recreate the element. // Type requires a different class of element. Recreate the element.
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{ [[MPAppDelegate managedObjectContext] performBlockAndWait:^{
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(
type)
inManagedObjectContext:[MPAppDelegate managedObjectContext]]; inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name; newElement.name = self.activeElement.name;
newElement.keyID = self.activeElement.keyID; newElement.user = self.activeElement.user;
newElement.uses = self.activeElement.uses; newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed; newElement.lastUsed = self.activeElement.lastUsed;
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement]; [[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
@@ -469,8 +555,6 @@
self.activeElement.type = type; self.activeElement.type = type;
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length]) if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon]; [self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}]; }];
@@ -478,23 +562,43 @@
- (void)didSelectElement:(MPElementEntity *)element { - (void)didSelectElement:(MPElementEntity *)element {
inf(@"Selected: %@", element.name);
[self closeAlert]; [self closeAlert];
if (element) { if (element) {
self.activeElement = element; self.activeElement = element;
if ([self.activeElement use] == 1) if ([self.activeElement use] == 1)
[self showAlertWithTitle:@"New Site" message: [self showAlertWithTitle:@"New Site" message:
PearlLocalize(@"You've just created a password for %@.\n\n" PearlString(@"You've just created a password for %@.\n\n"
@"IMPORTANT:\n" @"IMPORTANT:\n"
@"Go to %@ and set or change the password for your account to the password above.\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.", @"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)]; self.activeElement.name, self.activeElement.name)];
[[MPAppDelegate get] saveContext]; [[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 setActive:NO animated:YES];
self.searchDisplayController.searchBar.text = self.activeElement.name; 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]; [self updateAnimated:YES];
@@ -513,15 +617,15 @@
if (textField == self.contentField) { if (textField == self.contentField) {
self.contentField.enabled = NO; self.contentField.enabled = NO;
if (![self.activeElement isKindOfClass:[MPElementStoredEntity class]]) 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; return;
if ([((MPElementStoredEntity *) self.activeElement).content isEqual:self.contentField.text]) if ([((MPElementStoredEntity *)self.activeElement).content isEqual:self.contentField.text])
// Content hasn't changed. // Content hasn't changed.
return; return;
[self changeElementWithoutWarningDo:^{ [self changeElementWithoutWarningDo:^{
((MPElementStoredEntity *) self.activeElement).content = self.contentField.text; ((MPElementStoredEntity *)self.activeElement).content = self.contentField.text;
}]; }];
} }
} }
@@ -530,7 +634,8 @@
navigationType:(UIWebViewNavigationType)navigationType { navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) { if (navigationType == UIWebViewNavigationTypeLinkClicked) {
[TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink]; inf(@"External link: %@", [request URL]);
[TestFlight passCheckpoint:MPCheckpointExternalLink];
[[UIApplication sharedApplication] openURL:[request URL]]; [[UIApplication sharedApplication] openURL:[request URL]];
return NO; return NO;
@@ -539,10 +644,4 @@
return YES; return YES;
} }
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
while ([self.navigationController.viewControllers containsObject:sender])
[self.navigationController popViewControllerAnimated:YES];
}
@end @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 <Foundation/Foundation.h>
#import "MPElementEntity.h" #import "MPElementEntity.h"
@protocol MPSearchResultsDelegate <NSObject> @protocol MPSearchResultsDelegate<NSObject>
- (void)didSelectElement:(MPElementEntity *)element; - (void)didSelectElement:(MPElementEntity *)element;
@end @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) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSString *query; @property (strong, nonatomic) NSString *query;
@property (strong, nonatomic) UILabel *tipView; @property (strong, nonatomic) UILabel *tipView;
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate; @property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController; @property (strong, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer; @property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@end @end

View File

@@ -9,12 +9,11 @@
#import "MPSearchDelegate.h" #import "MPSearchDelegate.h"
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPElementGeneratedEntity.h" #import "LocalyticsSession.h"
@interface MPSearchDelegate (Private) @interface MPSearchDelegate (Private)
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath; - (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath;
- (void)update;
@end @end
@@ -32,32 +31,33 @@
if (!([super init])) if (!([super init]))
return nil; return nil;
self.dateFormatter = [NSDateFormatter new]; self.dateFormatter = [NSDateFormatter new];
self.dateFormatter.dateStyle = NSDateFormatterShortStyle; self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
self.query = @""; self.query = @"";
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext] managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil]; sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self; self.fetchedResultsController.delegate = self;
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)]; self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
self.tipView.textAlignment = UITextAlignmentCenter; self.tipView.textAlignment = UITextAlignmentCenter;
self.tipView.backgroundColor = [UIColor clearColor]; self.tipView.backgroundColor = [UIColor clearColor];
self.tipView.textColor = [UIColor lightTextColor]; self.tipView.textColor = [UIColor lightTextColor];
self.tipView.shadowColor = [UIColor blackColor]; self.tipView.shadowColor = [UIColor blackColor];
self.tipView.shadowOffset = CGSizeMake(0, -1); self.tipView.shadowOffset = CGSizeMake(0, -1);
self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin; self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
self.tipView.numberOfLines = 0; | UIViewAutoresizingFlexibleBottomMargin;
self.tipView.font = [UIFont systemFontOfSize:14]; self.tipView.numberOfLines = 0;
self.tipView.font = [UIFont systemFontOfSize:14];
self.tipView.text = self.tipView.text =
@"Tip:\n" @"Tip:\n"
@"Name your sites by their domain name:\n" @"Name your sites by their domain name:\n"
@"apple.com, twitter.com\n\n" @"apple.com, twitter.com\n\n"
@"For email accounts, use the address:\n" @"For email accounts, use the address:\n"
@"john@apple.com, john@gmail.com"; @"john@apple.com, john@gmail.com";
return self; return self;
} }
@@ -78,7 +78,7 @@
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch]; [TestFlight passCheckpoint:MPCheckpointCancelSearch];
[self.delegate didSelectElement:nil]; [self.delegate didSelectElement:nil];
} }
@@ -88,7 +88,7 @@
if (searchBar.searchResultsButtonSelected && !searchText.length) if (searchBar.searchResultsButtonSelected && !searchText.length)
searchBar.text = @" "; searchBar.text = @" ";
self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (!self.query) if (!self.query)
self.query = @""; self.query = @"";
} }
@@ -105,40 +105,36 @@
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller { - (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @""; controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
self.query = @""; self.query = @"";
} }
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller { - (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
controller.searchBar.prompt = nil; dbg(@"Search ended with: %@", controller.searchBar.text);
controller.searchBar.prompt = nil;
controller.searchBar.searchResultsButtonSelected = NO; controller.searchBar.searchResultsButtonSelected = NO;
} }
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView { - (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
tableView.backgroundColor = [UIColor blackColor]; tableView.backgroundColor = [UIColor blackColor];
tableView.separatorStyle = UITableViewCellSeparatorStyleNone; tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView.rowHeight = 48.0f; tableView.rowHeight = 48.0f;
} }
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self update]; if (!controller.active)
return NO;
return NO;
}
- (void)update {
assert(self.query); assert(self.query);
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND keyID == %@", self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
self.query, self.query, NilToNull([MPAppDelegate get].keyID)]; self.query, self.query, NilToNSNull([MPAppDelegate get].activeUser)];
NSError *error; NSError *error;
if (![self.fetchedResultsController performFetch:&error]) if (![self.fetchedResultsController performFetch:&error])
err(@"Couldn't fetch elements: %@", error); err(@"Couldn't fetch elements: %@", error);
[self.searchDisplayController.searchResultsTableView reloadData];
NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews; NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews;
NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1; NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1;
@@ -149,6 +145,8 @@
[self.tipView removeFromSuperview]; [self.tipView removeFromSuperview];
[overlay addSubview:self.tipView]; [overlay addSubview:self.tipView];
} }
return YES;
} }
// See MP-14, also crashes easily on internal assertions etc.. // See MP-14, also crashes easily on internal assertions etc..
@@ -219,28 +217,28 @@
__block BOOL hasExactQueryMatch = NO; __block BOOL hasExactQueryMatch = NO;
[sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id<NSFetchedResultsSectionInfo> sectionInfo = obj; id<NSFetchedResultsSectionInfo> sectionInfo = obj;
[[sectionInfo objects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [[sectionInfo objects] enumerateObjectsUsingBlock:^(id obj_, NSUInteger idx_, BOOL *stop_) {
if ([[obj name] isEqualToString:self.query]) { if ([[obj_ name] isEqualToString:self.query]) {
hasExactQueryMatch = YES; hasExactQueryMatch = YES;
*stop = YES; *stop_ = YES;
} }
}]; }];
if (hasExactQueryMatch) if (hasExactQueryMatch)
*stop = YES; *stop = YES;
}]; }];
if (!hasExactQueryMatch) if (!hasExactQueryMatch)
// Add a section for "new site". // Add a section for "new site".
++sectionCount; ++sectionCount;
} }
return (signed)sectionCount; return (NSInteger)sectionCount;
} }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *sections = [self.fetchedResultsController sections]; NSArray *sections = [self.fetchedResultsController sections];
if (section < (signed)[sections count]) if (section < (NSInteger)[sections count])
return (signed)[[sections objectAtIndex:(unsigned)section] numberOfObjects]; return (NSInteger)[[sections objectAtIndex:(unsigned)section] numberOfObjects];
return 1; return 1;
} }
@@ -252,20 +250,20 @@
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"]; cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]]; 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.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)]; UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)];
[backgroundView addSubview:backgroundImageView]; [backgroundView addSubview:backgroundImageView];
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.backgroundView = backgroundView; cell.backgroundView = backgroundView;
cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor]; cell.textLabel.textColor = [UIColor whiteColor];
cell.detailTextLabel.backgroundColor = [UIColor clearColor]; cell.detailTextLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor = [UIColor lightGrayColor]; cell.detailTextLabel.textColor = [UIColor lightGrayColor];
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.clipsToBounds = YES; cell.clipsToBounds = YES;
} }
[self configureCell:cell inTableView:tableView atIndexPath:indexPath]; [self configureCell:cell inTableView:tableView atIndexPath:indexPath];
@@ -275,56 +273,57 @@
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath { - (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]; 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 %@", 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 { } else {
// "New" section // "New" section
cell.textLabel.text = self.query; cell.textLabel.text = self.query;
cell.detailTextLabel.text = @"Create a new site."; cell.detailTextLabel.text = @"Create a new site.";
} }
} }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (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]]; [self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]];
else { else {
// "New" section. // "New" section.
NSString *siteName = self.query; NSString *siteName = self.query;
[PearlAlert showAlertWithTitle:@"New Site" [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 viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
[self.fetchedResultsController.managedObjectContext performBlock:^{ [self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class]) MPElementType type = [MPAppDelegate get].activeUser.defaultType;
inManagedObjectContext:self.fetchedResultsController.managedObjectContext]; MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]); inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([MPAppDelegate get].keyID); assert([MPAppDelegate get].activeUser);
element.name = siteName; element.name = siteName;
element.keyID = [MPAppDelegate get].keyID; element.user = [MPAppDelegate get].activeUser;
element.type = type;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didSelectElement:element]; [self.delegate didSelectElement:element];
}); });
}]; }];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
} }
} }
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - (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 [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name];
return @""; return @"";
@@ -340,15 +339,22 @@
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index]; return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
} }
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) { if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) {
if (editingStyle == UITableViewCellEditingStyleDelete) if (editingStyle == UITableViewCellEditingStyleDelete)
[self.fetchedResultsController.managedObjectContext performBlock:^{ [self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
inf(@"Deleting element: %@", element.name);
[self.fetchedResultsController.managedObjectContext deleteObject:element]; [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> #import <UIKit/UIKit.h>
@protocol MPTypeDelegate <NSObject> @protocol MPTypeDelegate<NSObject>
- (void)didSelectType:(MPElementType)type; - (void)didSelectType:(MPElementType)type;
@@ -20,5 +20,6 @@
@interface MPTypeViewController : UITableViewController @interface MPTypeViewController : UITableViewController
@property (nonatomic, weak) id<MPTypeDelegate> delegate; @property (nonatomic, weak) id<MPTypeDelegate> delegate;
@property (weak, nonatomic) IBOutlet UIView *recommendedTipContainer;
@end @end

View File

@@ -17,9 +17,36 @@
@implementation MPTypeViewController @implementation MPTypeViewController
@synthesize delegate; @synthesize delegate;
@synthesize recommendedTipContainer;
#pragma mark - View lifecycle #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 { - (void)viewDidLoad {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]]; self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
@@ -27,6 +54,12 @@
[super viewDidLoad]; [super viewDidLoad];
} }
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Type selection will disappear");
[super viewWillDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES; return YES;
@@ -37,18 +70,7 @@
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if ([delegate respondsToSelector:@selector(selectedType)]) if ([delegate respondsToSelector:@selector(selectedType)])
if ([delegate selectedType] == [self typeAtIndexPath:indexPath]) cell.selected = ([delegate selectedType] == [self typeAtIndexPath:indexPath]);
[cell iterateSubviewsContinueAfter:^BOOL(UIView *subview) {
if ([subview isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = ((UIImageView *)subview);
if (!imageView.highlightedImage)
imageView.highlightedImage = [imageView.image highlightedImage];
imageView.highlighted = YES;
return NO;
}
return YES;
}];
return cell; return cell;
} }
@@ -68,44 +90,52 @@
// Generated // Generated
switch (indexPath.row) { switch (indexPath.row) {
case 0: case 0:
return MPElementTypeGeneratedLong; return NSNotFound;
case 1: case 1:
return MPElementTypeGeneratedMedium; return MPElementTypeGeneratedMaximum;
case 2: case 2:
return MPElementTypeGeneratedShort; return MPElementTypeGeneratedLong;
case 3: case 3:
return MPElementTypeGeneratedBasic; return MPElementTypeGeneratedMedium;
case 4: case 4:
return MPElementTypeGeneratedShort;
case 5:
return MPElementTypeGeneratedBasic;
case 6:
return MPElementTypeGeneratedPIN; return MPElementTypeGeneratedPIN;
case 7:
return NSNotFound;
default: default:
[NSException raise:NSInternalInconsistencyException Throw(@"Unsupported row: %d, when selecting generated element type.", indexPath.row);
format:@"Unsupported row: %d, when selecting generated element type.", indexPath.row];
} }
break;
} }
case 1: { case 1: {
// Stored // Stored
switch (indexPath.row) { switch (indexPath.row) {
case 0: case 0:
return MPElementTypeStoredPersonal; return NSNotFound;
case 1: case 1:
return MPElementTypeStoredPersonal;
case 2:
return MPElementTypeStoredDevicePrivate; return MPElementTypeStoredDevicePrivate;
case 3:
return NSNotFound;
default: default:
[NSException raise:NSInternalInconsistencyException Throw(@"Unsupported row: %d, when selecting stored element type.", indexPath.row);
format:@"Unsupported row: %d, when selecting stored element type.", indexPath.row];
} }
break;
} }
default: default:
[NSException raise:NSInternalInconsistencyException Throw(@"Unsupported section: %d, when selecting element type.", indexPath.section);
format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
} }
@throw nil;
} }
- (void)viewDidUnload {
[self setRecommendedTipContainer:nil];
[super viewDidUnload];
}
@end @end

View File

@@ -8,14 +8,22 @@
#import <UIKit/UIKit.h> #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 UIImageView *spinner; @property (weak, nonatomic) IBOutlet UITextField *passwordField;
@property (weak, nonatomic) IBOutlet UITextField *field; @property (weak, nonatomic) IBOutlet UIView *passwordView;
@property (weak, nonatomic) IBOutlet UILabel *messageLabel; @property (weak, nonatomic) IBOutlet UIScrollView *avatarsView;
@property (weak, nonatomic) IBOutlet UIView *changeMPView; @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 @end

View File

@@ -12,77 +12,98 @@
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPElementEntity.h"
typedef enum {
MPLockscreenIdle,
MPLockscreenError,
MPLockscreenSuccess,
MPLockscreenProgress,
} MPLockscreen;
@interface MPUnlockViewController () @interface MPUnlockViewController ()
@property (strong, nonatomic) MPUserEntity *selectedUser;
@property (strong, nonatomic) NSMutableDictionary *avatarToUser;
@end @end
@implementation MPUnlockViewController @implementation MPUnlockViewController
@synthesize lock; @synthesize selectedUser;
@synthesize avatarToUser;
@synthesize spinner; @synthesize spinner;
@synthesize field; @synthesize passwordField;
@synthesize messageLabel; @synthesize passwordView;
@synthesize changeMPView; @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) = ^{ - (void)initAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
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; UIScrollView *alertAvatarScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(12, 30, 260, 150)];
[UIView animateWithDuration:1.0f animations:^{ alertAvatarScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
self.lock.alpha = 1.0f; [alertAvatarScrollView flashScrollIndicatorsContinuously];
} completion:^(BOOL finished) { [alert addSubview:alertAvatarScrollView];
if (finished)
[UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
self.lock.alpha = 0.5f;
} completion:nil];
}];
[UIView animateWithDuration:0.5f animations:^{ CGPoint selectedOffset = CGPointZero;
self.messageLabel.alpha = 1.0f; for (int a = 0; a < MPAvatarCount; ++a) {
self.messageLabel.text = message; UIButton *avatar = [self.avatarTemplate cloneAddedTo:alertAvatarScrollView];
}];
};
if (self.messageLabel.alpha) avatar.tag = a;
[UIView animateWithDuration:0.3f animations:^{ avatar.hidden = NO;
self.messageLabel.alpha = 0.0f; avatar.center = CGPointMake(
} completion:^(BOOL finished) { (20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
if (finished) 20 + self.avatarTemplate.bounds.size.height / 2);
showMessageAnimation(); [avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)] forState:UIControlStateNormal];
}]; [avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
else
showMessageAnimation(); 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 { - (void)initConfirmationAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
[UIView animateWithDuration:0.5f animations:^{ UIView *container = [[UIView alloc] initWithFrame:CGRectMake(12, 70, 260, 110)];
self.messageLabel.alpha = 0.0f; [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 { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
@@ -92,16 +113,16 @@ typedef enum {
- (void)viewDidLoad { - (void)viewDidLoad {
self.messageLabel.text = nil; self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3];
self.messageLabel.alpha = 0;
self.changeMPView.alpha = 0;
self.spinner.alpha = 0;
self.field.text = nil;
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten self.avatarsView.decelerationRate = UIScrollViewDecelerationRateFast;
object:nil queue:nil usingBlock:^(NSNotification *note) { self.avatarsView.clipsToBounds = NO;
[self.field becomeFirstResponder]; 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]; [super viewDidLoad];
} }
@@ -109,148 +130,507 @@ typedef enum {
- (void)viewDidUnload { - (void)viewDidUnload {
[self setSpinner:nil]; [self setSpinner:nil];
[self setField:nil]; [self setPasswordField:nil];
[self setPasswordView:nil];
[self setMessageLabel:nil]; [self setAvatarsView:nil];
[self setLock:nil]; [self setNameLabel:nil];
[self setChangeMPView:nil]; [self setAvatarTemplate:nil];
[self setDeleteTip:nil];
[self setPasswordTipView:nil];
[self setPasswordTipLabel:nil];
[self setTargetedUserActionGesture:nil];
[super viewDidUnload]; [super viewDidUnload];
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:YES inf(@"Lock screen will appear");
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone]; self.selectedUser = nil;
[self updateUsers];
[super viewWillAppear:animated]; [super viewWillAppear:animated];
} }
- (void)viewWillDisappear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:NO
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewWillDisappear:animated];
}
- (void)viewDidAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated {
[self.field becomeFirstResponder]; [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewDidAppear:animated]; [super viewDidAppear:animated];
} }
- (BOOL)textFieldShouldReturn:(UITextField *)textField { - (void)viewWillDisappear:(BOOL)animated {
if ([textField.text length]) { inf(@"Lock screen will disappear");
[textField resignFirstResponder]; [super viewWillDisappear:animated];
return YES;
}
return NO;
} }
- (void)textFieldDidEndEditing:(UITextField *)textField { - (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
[self didSelectNewUserAvatar:avatar];
} else {
self.selectedUser = nil;
[self didToggleUserSelection];
}
} 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), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@try { BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text];
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]; dispatch_async(dispatch_get_main_queue(), ^{
[self.spinner.layer addAnimation:rotate forKey:@"transform"]; if (unlocked)
[self dismissViewControllerAnimated:YES completion:nil];
[UIView animateWithDuration:0.3f animations:^{ else {
self.spinner.alpha = 1.0f; if (self.passwordField.text.length)
}]; [self setPasswordTip:@"Incorrect password."];
[self showMessage:@"Checking password..." state:MPLockscreenProgress]; [self setSpinnerActive:NO];
}); }
});
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];
}
});
else
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Not valid." state:MPLockscreenError];
[UIView animateWithDuration:0.5f animations:^{
self.changeMPView.alpha = 1.0f;
}];
});
}
@finally {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 0.0f;
} completion:^(BOOL finished) {
[self.spinner.layer removeAllAnimations];
}];
});
}
}); });
} }
- (IBAction)changeMP { - (UIButton *)findTargetedAvatar {
[PearlAlert showAlertWithTitle:@"Changing Master Password" CGFloat xOfMiddle = self.avatarsView.contentOffset.x + self.avatarsView.bounds.size.width / 2;
message: return (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, self.avatarsView.contentOffset.y)
@"This will allow you to log in with a different master password.\n\n" ofArray:self.avatarsView.subviews];
@"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 *)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 @end

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,15 @@
<true/> <true/>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>© 2011-2012, Lyndir</string> <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> <key>UIAppFonts</key>
<array> <array>
<string>Exo-Black.otf</string> <string>Exo-Black.otf</string>

View File

@@ -11,12 +11,18 @@
#import "Pearl-Prefix.pch" #import "Pearl-Prefix.pch"
#ifdef __OBJC__ #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 #endif

View File

@@ -6,7 +6,7 @@
<array> <array>
<dict> <dict>
<key>FooterText</key> <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> <key>Title</key>
<string></string> <string></string>
<key>Type</key> <key>Type</key>
@@ -42,9 +42,19 @@
<key>Key</key> <key>Key</key>
<string>unset</string> <string>unset</string>
</dict> </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> <dict>
<key>FooterText</key> <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> <key>Title</key>
<string>Master Password</string> <string>Master Password</string>
<key>Type</key> <key>Type</key>
@@ -54,27 +64,9 @@
<key>DefaultValue</key> <key>DefaultValue</key>
<false/> <false/>
<key>Key</key> <key>Key</key>
<string>rememberKey</string> <string>rememberLogin</string>
<key>Title</key> <key>Title</key>
<string>Remember my password</string> <string>Stay logged in</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>
<key>Type</key> <key>Type</key>
<string>PSToggleSwitchSpecifier</string> <string>PSToggleSwitchSpecifier</string>
</dict> </dict>

View File

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