Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dba4c87ba | ||
|
|
6841ae2b0d | ||
|
|
a3698b9e47 | ||
|
|
5b65c8c6bd | ||
|
|
94c9d50a12 | ||
|
|
5849c9668f | ||
|
|
4d4ba3425e | ||
|
|
b7e91358be | ||
|
|
da860d74c4 | ||
|
|
4e2ceb33a0 | ||
|
|
d14bde07bd | ||
|
|
fa2dc822d3 | ||
|
|
d4adafb448 | ||
|
|
a67d9676ba | ||
|
|
bc2da6a99b | ||
|
|
50b5c87f61 | ||
|
|
d4bcad2658 | ||
|
|
13bca309ba | ||
|
|
83bdb9d626 | ||
|
|
5f1fc453d4 | ||
|
|
9c875d4311 | ||
|
|
afafa473a7 | ||
|
|
4d5f609d72 | ||
|
|
6f1d53ea35 | ||
|
|
a8bf74a925 | ||
|
|
d59f77720c | ||
|
|
92be7f7267 | ||
|
|
09d5e64c55 | ||
|
|
f796888901 | ||
|
|
679990dc4b | ||
|
|
b472c85c9d | ||
|
|
77306e0046 | ||
|
|
0491ba3f97 | ||
|
|
ba299d4674 | ||
|
|
3de9a0c67e | ||
|
|
7d0ea4b3f5 | ||
|
|
ac14b10752 | ||
|
|
2d8f2943e5 | ||
|
|
4c0348b5c8 | ||
|
|
a5d2f82db4 | ||
|
|
ec8eff2117 |
5
.gitmodules
vendored
@@ -6,4 +6,7 @@
|
||||
url = git://github.com/futuretap/InAppSettingsKit.git
|
||||
[submodule "External/iCloudStoreManager"]
|
||||
path = External/iCloudStoreManager
|
||||
url = git://github.com/alekseyn/iCloudStoreManager.git
|
||||
url = git://github.com/lhunath/iCloudStoreManager.git
|
||||
[submodule "External/FontReplacer"]
|
||||
path = External/FontReplacer
|
||||
url = git://github.com/0xced/FontReplacer.git
|
||||
|
||||
9
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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>
|
||||
@@ -7,6 +7,46 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
*
|
||||
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
|
||||
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
|
||||
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
|
||||
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
|
||||
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
|
||||
*
|
||||
* Example output:
|
||||
* -[AppDelegate login:] line 134 $ login start
|
||||
*
|
||||
* If you would like to change this macro, create a new header file, unset our define and then define
|
||||
* your own version. Make sure this new header file is imported after the Crashlytics header file.
|
||||
*
|
||||
* #undef CLS_LOG
|
||||
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
|
||||
*
|
||||
**/
|
||||
#ifdef DEBUG
|
||||
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
|
||||
#else
|
||||
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
|
||||
* and will only be visible in your Crashlytics dashboard.
|
||||
*
|
||||
**/
|
||||
void CLSLog(NSString *format, ...);
|
||||
|
||||
/**
|
||||
*
|
||||
* Add logging that will be sent with your crash data. This logging will show up in the system.log
|
||||
* and your Crashlytics dashboard. It is not reccomended for Release builds.
|
||||
*
|
||||
**/
|
||||
void CLSNSLog(NSString *format, ...);
|
||||
|
||||
@protocol CrashlyticsDelegate;
|
||||
|
||||
@interface Crashlytics : NSObject {
|
||||
@@ -63,6 +103,49 @@
|
||||
**/
|
||||
- (void)crash;
|
||||
|
||||
/**
|
||||
*
|
||||
* Many of our customers have requested the ability to tie crashes to specific end-users of their
|
||||
* application in order to facilitate responses to support requests or permit the ability to reach
|
||||
* out for more information. We allow you to specify up to three separate values for display within
|
||||
* the Crashlytics UI - but please be mindful of your end-user's privacy.
|
||||
*
|
||||
* We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record
|
||||
* in your system. This could be a database id, hash, or other value that is meaningless to a
|
||||
* third-party observer but can be indexed and queried by you.
|
||||
*
|
||||
* Optionally, you may also specify the end-user's name or username, as well as email address if you
|
||||
* do not have a system that works well with obscured identifiers.
|
||||
*
|
||||
* Pursuant to our EULA, this data is transferred securely throughout our system and we will not
|
||||
* disseminate end-user data unless required to by law. That said, if you choose to provide end-user
|
||||
* contact information, we strongly recommend that you disclose this in your application's privacy
|
||||
* policy. Data privacy is of our utmost concern.
|
||||
*
|
||||
**/
|
||||
- (void)setUserIdentifier:(NSString *)identifier;
|
||||
- (void)setUserName:(NSString *)name;
|
||||
- (void)setUserEmail:(NSString *)email;
|
||||
|
||||
+ (void)setUserIdentifier:(NSString *)identifier;
|
||||
+ (void)setUserName:(NSString *)name;
|
||||
+ (void)setUserEmail:(NSString *)email;
|
||||
|
||||
/**
|
||||
*
|
||||
* Set a value for a key to be associated with your crash data.
|
||||
*
|
||||
**/
|
||||
- (void)setObjectValue:(id)value forKey:(NSString *)key;
|
||||
- (void)setIntValue:(int)value forKey:(NSString *)key;
|
||||
- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
|
||||
- (void)setFloatValue:(float)value forKey:(NSString *)key;
|
||||
|
||||
+ (void)setObjectValue:(id)value forKey:(NSString *)key;
|
||||
+ (void)setIntValue:(int)value forKey:(NSString *)key;
|
||||
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
|
||||
+ (void)setFloatValue:(float)value forKey:(NSString *)key;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
@@ -80,7 +163,7 @@
|
||||
*
|
||||
* Called once a Crashlytics instance has determined that the last execution of the
|
||||
* application ended in a crash. This is called some time after the crash reporting
|
||||
* process has begun. If you have specififed a delay in one of the
|
||||
* process has begun. If you have specified a delay in one of the
|
||||
* startWithAPIKey:... calls, this will take at least that long to be invoked.
|
||||
*
|
||||
**/
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1.3</string>
|
||||
<string>1.1.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
@@ -25,7 +25,7 @@
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0100.01.03</string>
|
||||
<string>0101.02.00</string>
|
||||
<key>CrashlyticsAPIKey</key>
|
||||
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
|
||||
<key>DTCompiler</key>
|
||||
|
||||
BIN
Crashlytics/Crashlytics.framework/run
vendored
1
External/FontReplacer
vendored
Submodule
2
External/Pearl
vendored
2
External/iCloudStoreManager
vendored
@@ -10,14 +10,20 @@
|
||||
DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
|
||||
DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; };
|
||||
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; };
|
||||
DA0A1D0D15690AD40092735D /* tip_arrow_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0715690AD40092735D /* tip_arrow_banana.png */; };
|
||||
DA0A1D0E15690AD40092735D /* tip_arrow_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0815690AD40092735D /* tip_arrow_black.png */; };
|
||||
DA0A1D0F15690AD40092735D /* tip_arrow_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0915690AD40092735D /* tip_arrow_gray.png */; };
|
||||
DA0A1D1015690AD40092735D /* tip_arrow_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0A15690AD40092735D /* tip_arrow_mercury.png */; };
|
||||
DA0A1D1115690AD40092735D /* tip_arrow_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0B15690AD40092735D /* tip_arrow_teal.png */; };
|
||||
DA0A1D1215690AD40092735D /* tip_arrow_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */; };
|
||||
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; };
|
||||
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; };
|
||||
DA0E07961577FE490008A67E /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0E07951577FE490008A67E /* MPEntities.m */; };
|
||||
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
|
||||
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
|
||||
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
|
||||
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */; };
|
||||
DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */; };
|
||||
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9D515723E6900A68B4C /* PearlLazy.h */; };
|
||||
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
|
||||
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2601586099D0079CE6E /* MPUserEntity.m */; };
|
||||
DA40C2641586099E0079CE6E /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2631586099E0079CE6E /* MPElementEntity.m */; };
|
||||
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */; };
|
||||
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */; };
|
||||
DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA4426001557BF260052177D /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DA4425F11557BF260052177D /* UbiquityStoreManager.h */; };
|
||||
DA4426011557BF260052177D /* UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4425F21557BF260052177D /* UbiquityStoreManager.m */; };
|
||||
@@ -35,6 +41,10 @@
|
||||
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA600C2715056427008E9AB6 /* MPConfig.m */; };
|
||||
DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; };
|
||||
DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
|
||||
DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA829E6015984813002417D3 /* UIFont+Replacement.h in Headers */ = {isa = PBXBuildFile; fileRef = DA829E5E15984812002417D3 /* UIFont+Replacement.h */; };
|
||||
DA829E6115984813002417D3 /* UIFont+Replacement.m in Sources */ = {isa = PBXBuildFile; fileRef = DA829E5F15984812002417D3 /* UIFont+Replacement.m */; };
|
||||
DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA829E51159847E0002417D3 /* libFontReplacer.a */; };
|
||||
DA95D59D14DF063C008D1B94 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA95D5CF14DF0691008D1B94 /* IASKAppSettingsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */; };
|
||||
DA95D5D014DF0691008D1B94 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */; };
|
||||
@@ -87,10 +97,7 @@
|
||||
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
|
||||
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
|
||||
DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; };
|
||||
DAB8D46B15036BCF00CED3BC /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */; };
|
||||
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45615036BCF00CED3BC /* MPTypes.m */; };
|
||||
DAB8D46D15036BCF00CED3BC /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45815036BCF00CED3BC /* MPElementEntity.m */; };
|
||||
DAB8D46E15036BCF00CED3BC /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */; };
|
||||
DAB8D6FA15036BF600CED3BC /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47115036BF600CED3BC /* ui_background.png */; };
|
||||
DAB8D6FB15036BF600CED3BC /* ui_background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47215036BF600CED3BC /* ui_background@2x.png */; };
|
||||
DAB8D6FC15036BF600CED3BC /* ui_box_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47315036BF600CED3BC /* ui_box_checked.png */; };
|
||||
@@ -661,72 +668,6 @@
|
||||
DAB8D93715036BF700CED3BC /* lock_red.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B415036BF600CED3BC /* lock_red.png */; };
|
||||
DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B515036BF600CED3BC /* lock_red@2x.png */; };
|
||||
DAB8D93915036BF700CED3BC /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B615036BF600CED3BC /* logo-bare.png */; };
|
||||
DAB8D93A15036BF700CED3BC /* tip_alert_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B815036BF600CED3BC /* tip_alert_banana.png */; };
|
||||
DAB8D93B15036BF700CED3BC /* tip_alert_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B915036BF600CED3BC /* tip_alert_banana@2x.png */; };
|
||||
DAB8D93C15036BF700CED3BC /* tip_alert_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */; };
|
||||
DAB8D93D15036BF700CED3BC /* tip_alert_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BB15036BF600CED3BC /* tip_alert_black@2x.png */; };
|
||||
DAB8D93E15036BF700CED3BC /* tip_alert_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BC15036BF600CED3BC /* tip_alert_gray.png */; };
|
||||
DAB8D93F15036BF700CED3BC /* tip_alert_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BD15036BF600CED3BC /* tip_alert_gray@2x.png */; };
|
||||
DAB8D94015036BF700CED3BC /* tip_alert_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BE15036BF600CED3BC /* tip_alert_mercury.png */; };
|
||||
DAB8D94115036BF700CED3BC /* tip_alert_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BF15036BF600CED3BC /* tip_alert_mercury@2x.png */; };
|
||||
DAB8D94215036BF700CED3BC /* tip_alert_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C015036BF600CED3BC /* tip_alert_teal.png */; };
|
||||
DAB8D94315036BF700CED3BC /* tip_alert_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C115036BF600CED3BC /* tip_alert_teal@2x.png */; };
|
||||
DAB8D94415036BF700CED3BC /* tip_alert_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C215036BF600CED3BC /* tip_alert_wood.png */; };
|
||||
DAB8D94515036BF700CED3BC /* tip_alert_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C315036BF600CED3BC /* tip_alert_wood@2x.png */; };
|
||||
DAB8D94615036BF700CED3BC /* tip_arrow_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C415036BF600CED3BC /* tip_arrow_banana@2x.png */; };
|
||||
DAB8D94715036BF700CED3BC /* tip_arrow_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C515036BF600CED3BC /* tip_arrow_black@2x.png */; };
|
||||
DAB8D94815036BF700CED3BC /* tip_arrow_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C615036BF600CED3BC /* tip_arrow_gray@2x.png */; };
|
||||
DAB8D94915036BF700CED3BC /* tip_arrow_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C715036BF600CED3BC /* tip_arrow_mercury@2x.png */; };
|
||||
DAB8D94A15036BF700CED3BC /* tip_arrow_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C815036BF600CED3BC /* tip_arrow_teal@2x.png */; };
|
||||
DAB8D94B15036BF700CED3BC /* tip_arrow_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6C915036BF600CED3BC /* tip_arrow_wood@2x.png */; };
|
||||
DAB8D94C15036BF700CED3BC /* tip_basic_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CA15036BF600CED3BC /* tip_basic_banana.png */; };
|
||||
DAB8D94D15036BF700CED3BC /* tip_basic_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CB15036BF600CED3BC /* tip_basic_banana@2x.png */; };
|
||||
DAB8D94E15036BF700CED3BC /* tip_basic_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CC15036BF600CED3BC /* tip_basic_black.png */; };
|
||||
DAB8D94F15036BF700CED3BC /* tip_basic_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CD15036BF600CED3BC /* tip_basic_black@2x.png */; };
|
||||
DAB8D95015036BF700CED3BC /* tip_basic_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CE15036BF600CED3BC /* tip_basic_gray.png */; };
|
||||
DAB8D95115036BF700CED3BC /* tip_basic_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CF15036BF600CED3BC /* tip_basic_gray@2x.png */; };
|
||||
DAB8D95215036BF700CED3BC /* tip_basic_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D015036BF600CED3BC /* tip_basic_mercury.png */; };
|
||||
DAB8D95315036BF700CED3BC /* tip_basic_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D115036BF600CED3BC /* tip_basic_mercury@2x.png */; };
|
||||
DAB8D95415036BF700CED3BC /* tip_basic_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D215036BF600CED3BC /* tip_basic_teal.png */; };
|
||||
DAB8D95515036BF700CED3BC /* tip_basic_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D315036BF600CED3BC /* tip_basic_teal@2x.png */; };
|
||||
DAB8D95615036BF700CED3BC /* tip_basic_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D415036BF600CED3BC /* tip_basic_wood.png */; };
|
||||
DAB8D95715036BF700CED3BC /* tip_basic_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D515036BF600CED3BC /* tip_basic_wood@2x.png */; };
|
||||
DAB8D95815036BF700CED3BC /* tip_download_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D615036BF600CED3BC /* tip_download_banana.png */; };
|
||||
DAB8D95915036BF700CED3BC /* tip_download_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D715036BF600CED3BC /* tip_download_banana@2x.png */; };
|
||||
DAB8D95A15036BF700CED3BC /* tip_download_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D815036BF600CED3BC /* tip_download_black.png */; };
|
||||
DAB8D95B15036BF700CED3BC /* tip_download_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6D915036BF600CED3BC /* tip_download_black@2x.png */; };
|
||||
DAB8D95C15036BF700CED3BC /* tip_download_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DA15036BF600CED3BC /* tip_download_gray.png */; };
|
||||
DAB8D95D15036BF700CED3BC /* tip_download_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DB15036BF600CED3BC /* tip_download_gray@2x.png */; };
|
||||
DAB8D95E15036BF700CED3BC /* tip_download_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DC15036BF600CED3BC /* tip_download_mercury.png */; };
|
||||
DAB8D95F15036BF700CED3BC /* tip_download_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DD15036BF600CED3BC /* tip_download_mercury@2x.png */; };
|
||||
DAB8D96015036BF700CED3BC /* tip_download_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DE15036BF600CED3BC /* tip_download_teal.png */; };
|
||||
DAB8D96115036BF700CED3BC /* tip_download_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6DF15036BF600CED3BC /* tip_download_teal@2x.png */; };
|
||||
DAB8D96215036BF700CED3BC /* tip_download_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E015036BF600CED3BC /* tip_download_wood.png */; };
|
||||
DAB8D96315036BF700CED3BC /* tip_download_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E115036BF600CED3BC /* tip_download_wood@2x.png */; };
|
||||
DAB8D96415036BF700CED3BC /* tip_large_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E215036BF600CED3BC /* tip_large_banana.png */; };
|
||||
DAB8D96515036BF700CED3BC /* tip_large_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E315036BF600CED3BC /* tip_large_banana@2x.png */; };
|
||||
DAB8D96615036BF700CED3BC /* tip_large_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E415036BF600CED3BC /* tip_large_black.png */; };
|
||||
DAB8D96715036BF700CED3BC /* tip_large_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E515036BF600CED3BC /* tip_large_black@2x.png */; };
|
||||
DAB8D96815036BF700CED3BC /* tip_large_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E615036BF600CED3BC /* tip_large_gray.png */; };
|
||||
DAB8D96915036BF700CED3BC /* tip_large_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E715036BF600CED3BC /* tip_large_gray@2x.png */; };
|
||||
DAB8D96A15036BF700CED3BC /* tip_large_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E815036BF600CED3BC /* tip_large_mercury.png */; };
|
||||
DAB8D96B15036BF700CED3BC /* tip_large_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6E915036BF600CED3BC /* tip_large_mercury@2x.png */; };
|
||||
DAB8D96C15036BF700CED3BC /* tip_large_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EA15036BF600CED3BC /* tip_large_teal.png */; };
|
||||
DAB8D96D15036BF700CED3BC /* tip_large_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EB15036BF600CED3BC /* tip_large_teal@2x.png */; };
|
||||
DAB8D96E15036BF700CED3BC /* tip_large_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EC15036BF600CED3BC /* tip_large_wood.png */; };
|
||||
DAB8D96F15036BF700CED3BC /* tip_large_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6ED15036BF600CED3BC /* tip_large_wood@2x.png */; };
|
||||
DAB8D97015036BF700CED3BC /* tip_location_banana.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EE15036BF600CED3BC /* tip_location_banana.png */; };
|
||||
DAB8D97115036BF700CED3BC /* tip_location_banana@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6EF15036BF600CED3BC /* tip_location_banana@2x.png */; };
|
||||
DAB8D97215036BF700CED3BC /* tip_location_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F015036BF600CED3BC /* tip_location_black.png */; };
|
||||
DAB8D97315036BF700CED3BC /* tip_location_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F115036BF600CED3BC /* tip_location_black@2x.png */; };
|
||||
DAB8D97415036BF700CED3BC /* tip_location_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F215036BF600CED3BC /* tip_location_gray.png */; };
|
||||
DAB8D97515036BF700CED3BC /* tip_location_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F315036BF600CED3BC /* tip_location_gray@2x.png */; };
|
||||
DAB8D97615036BF700CED3BC /* tip_location_mercury.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F415036BF600CED3BC /* tip_location_mercury.png */; };
|
||||
DAB8D97715036BF700CED3BC /* tip_location_mercury@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F515036BF600CED3BC /* tip_location_mercury@2x.png */; };
|
||||
DAB8D97815036BF700CED3BC /* tip_location_teal.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F615036BF600CED3BC /* tip_location_teal.png */; };
|
||||
DAB8D97915036BF700CED3BC /* tip_location_teal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F715036BF600CED3BC /* tip_location_teal@2x.png */; };
|
||||
DAB8D97A15036BF700CED3BC /* tip_location_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F815036BF600CED3BC /* tip_location_wood.png */; };
|
||||
DAB8D97B15036BF700CED3BC /* tip_location_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F915036BF600CED3BC /* tip_location_wood@2x.png */; };
|
||||
DAB8D97C1503718B00CED3BC /* jquery-1.6.1.min.js in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6AB15036BF600CED3BC /* jquery-1.6.1.min.js */; };
|
||||
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
|
||||
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
@@ -734,7 +675,16 @@
|
||||
DAC6327B1486809A0075AEA5 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC632791486809A0075AEA5 /* JRSwizzle.h */; };
|
||||
DAC6327C1486809A0075AEA5 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC6327A1486809A0075AEA5 /* JRSwizzle.m */; };
|
||||
DAC632891486D9690075AEA5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; };
|
||||
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */; };
|
||||
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DACABB861572A2A7008BA211 /* tip_alert_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */; };
|
||||
DACABB871572A2A7008BA211 /* tip_alert_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BB15036BF600CED3BC /* tip_alert_black@2x.png */; };
|
||||
DACABB881572A2A7008BA211 /* tip_basic_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CC15036BF600CED3BC /* tip_basic_black.png */; };
|
||||
DACABB891572A2A7008BA211 /* tip_basic_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6CD15036BF600CED3BC /* tip_basic_black@2x.png */; };
|
||||
DACABB8C1572A4A5008BA211 /* tip_basic_black_top_right.png in Resources */ = {isa = PBXBuildFile; fileRef = DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */; };
|
||||
DACABB8D1572A4A5008BA211 /* tip_basic_black_top_right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACABB8B1572A4A4008BA211 /* tip_basic_black_top_right@2x.png */; };
|
||||
DACABB901572B76A008BA211 /* tip_basic_black_top.png in Resources */ = {isa = PBXBuildFile; fileRef = DACABB8E1572B769008BA211 /* tip_basic_black_top.png */; };
|
||||
DACABB911572B76A008BA211 /* tip_basic_black_top@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */; };
|
||||
DAD3126715528C9C00A3F9ED /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3125F15528C9C00A3F9ED /* Crashlytics.framework */; };
|
||||
DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */ = {isa = PBXBuildFile; fileRef = DAD3126015528C9C00A3F9ED /* Crashlytics.plist */; };
|
||||
DAD3126915528C9C00A3F9ED /* libTestFlight.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3126215528C9C00A3F9ED /* libTestFlight.a */; };
|
||||
@@ -756,13 +706,51 @@
|
||||
DAD312BE1552977200A3F9ED /* UIColor+HSV.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD312BA1552977200A3F9ED /* UIColor+HSV.m */; };
|
||||
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3127115528CD200A3F9ED /* libLocalytics.a */; };
|
||||
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD312C01552A20800A3F9ED /* libsqlite3.dylib */; };
|
||||
DAE4C969157E63BE00EFE047 /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C943157E63BE00EFE047 /* avatar-0.png */; };
|
||||
DAE4C96A157E63BE00EFE047 /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C944157E63BE00EFE047 /* avatar-0@2x.png */; };
|
||||
DAE4C96B157E63BE00EFE047 /* avatar-1.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C945157E63BE00EFE047 /* avatar-1.png */; };
|
||||
DAE4C96C157E63BE00EFE047 /* avatar-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C946157E63BE00EFE047 /* avatar-1@2x.png */; };
|
||||
DAE4C96D157E63BE00EFE047 /* avatar-2.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C947157E63BE00EFE047 /* avatar-2.png */; };
|
||||
DAE4C96E157E63BE00EFE047 /* avatar-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C948157E63BE00EFE047 /* avatar-2@2x.png */; };
|
||||
DAE4C96F157E63BE00EFE047 /* avatar-3.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C949157E63BE00EFE047 /* avatar-3.png */; };
|
||||
DAE4C970157E63BE00EFE047 /* avatar-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94A157E63BE00EFE047 /* avatar-3@2x.png */; };
|
||||
DAE4C971157E63BE00EFE047 /* avatar-4.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94B157E63BE00EFE047 /* avatar-4.png */; };
|
||||
DAE4C972157E63BE00EFE047 /* avatar-4@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94C157E63BE00EFE047 /* avatar-4@2x.png */; };
|
||||
DAE4C973157E63BE00EFE047 /* avatar-5.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94D157E63BE00EFE047 /* avatar-5.png */; };
|
||||
DAE4C974157E63BE00EFE047 /* avatar-5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94E157E63BE00EFE047 /* avatar-5@2x.png */; };
|
||||
DAE4C975157E63BE00EFE047 /* avatar-6.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C94F157E63BE00EFE047 /* avatar-6.png */; };
|
||||
DAE4C976157E63BE00EFE047 /* avatar-6@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C950157E63BE00EFE047 /* avatar-6@2x.png */; };
|
||||
DAE4C977157E63BE00EFE047 /* avatar-7.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C951157E63BE00EFE047 /* avatar-7.png */; };
|
||||
DAE4C978157E63BE00EFE047 /* avatar-7@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C952157E63BE00EFE047 /* avatar-7@2x.png */; };
|
||||
DAE4C979157E63BE00EFE047 /* avatar-8.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C953157E63BE00EFE047 /* avatar-8.png */; };
|
||||
DAE4C97A157E63BE00EFE047 /* avatar-8@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C954157E63BE00EFE047 /* avatar-8@2x.png */; };
|
||||
DAE4C97B157E63BE00EFE047 /* avatar-9.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C955157E63BE00EFE047 /* avatar-9.png */; };
|
||||
DAE4C97C157E63BE00EFE047 /* avatar-9@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C956157E63BE00EFE047 /* avatar-9@2x.png */; };
|
||||
DAE4C97D157E63BE00EFE047 /* avatar-10.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C957157E63BE00EFE047 /* avatar-10.png */; };
|
||||
DAE4C97E157E63BE00EFE047 /* avatar-10@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C958157E63BE00EFE047 /* avatar-10@2x.png */; };
|
||||
DAE4C97F157E63BE00EFE047 /* avatar-11.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C959157E63BE00EFE047 /* avatar-11.png */; };
|
||||
DAE4C980157E63BE00EFE047 /* avatar-11@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95A157E63BE00EFE047 /* avatar-11@2x.png */; };
|
||||
DAE4C981157E63BE00EFE047 /* avatar-12.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95B157E63BE00EFE047 /* avatar-12.png */; };
|
||||
DAE4C982157E63BE00EFE047 /* avatar-12@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95C157E63BE00EFE047 /* avatar-12@2x.png */; };
|
||||
DAE4C983157E63BE00EFE047 /* avatar-13.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95D157E63BE00EFE047 /* avatar-13.png */; };
|
||||
DAE4C984157E63BE00EFE047 /* avatar-13@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95E157E63BE00EFE047 /* avatar-13@2x.png */; };
|
||||
DAE4C985157E63BE00EFE047 /* avatar-14.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C95F157E63BE00EFE047 /* avatar-14.png */; };
|
||||
DAE4C986157E63BE00EFE047 /* avatar-14@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C960157E63BE00EFE047 /* avatar-14@2x.png */; };
|
||||
DAE4C987157E63BE00EFE047 /* avatar-15.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C961157E63BE00EFE047 /* avatar-15.png */; };
|
||||
DAE4C988157E63BE00EFE047 /* avatar-15@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C962157E63BE00EFE047 /* avatar-15@2x.png */; };
|
||||
DAE4C989157E63BE00EFE047 /* avatar-16.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C963157E63BE00EFE047 /* avatar-16.png */; };
|
||||
DAE4C98A157E63BE00EFE047 /* avatar-16@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C964157E63BE00EFE047 /* avatar-16@2x.png */; };
|
||||
DAE4C98B157E63BE00EFE047 /* avatar-17.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C965157E63BE00EFE047 /* avatar-17.png */; };
|
||||
DAE4C98C157E63BE00EFE047 /* avatar-17@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C966157E63BE00EFE047 /* avatar-17@2x.png */; };
|
||||
DAE4C98D157E63BE00EFE047 /* avatar-18.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C967157E63BE00EFE047 /* avatar-18.png */; };
|
||||
DAE4C98E157E63BE00EFE047 /* avatar-18@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE4C968157E63BE00EFE047 /* avatar-18@2x.png */; };
|
||||
DAEBC45314F6364500987BF6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; };
|
||||
DAFE4A1315039824003ABA7C /* NSObject_PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */; };
|
||||
DAFE4A1415039824003ABA7C /* NSObject_PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */; };
|
||||
DAFE4A1515039824003ABA7C /* NSString_PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString_PearlNSArrayFormat.h */; };
|
||||
DAFE4A1615039824003ABA7C /* NSString_PearlNSArrayFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DB15039823003ABA7C /* NSString_PearlNSArrayFormat.m */; };
|
||||
DAFE4A1715039824003ABA7C /* NSString_PearlSEL.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DC15039823003ABA7C /* NSString_PearlSEL.h */; };
|
||||
DAFE4A1815039824003ABA7C /* NSString_PearlSEL.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DD15039823003ABA7C /* NSString_PearlSEL.m */; };
|
||||
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */; };
|
||||
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */; };
|
||||
DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */; };
|
||||
DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DB15039823003ABA7C /* NSString+PearlNSArrayFormat.m */; };
|
||||
DAFE4A1715039824003ABA7C /* NSString+PearlSEL.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DC15039823003ABA7C /* NSString+PearlSEL.h */; };
|
||||
DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45DD15039823003ABA7C /* NSString+PearlSEL.m */; };
|
||||
DAFE4A1915039824003ABA7C /* Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DE15039823003ABA7C /* Pearl.h */; };
|
||||
DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */; };
|
||||
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */; };
|
||||
@@ -822,11 +810,21 @@
|
||||
DAFE4A5515039824003ABA7C /* PearlValidatingTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE462115039823003ABA7C /* PearlValidatingTextField.m */; };
|
||||
DAFE4A5615039824003ABA7C /* PearlWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE462215039823003ABA7C /* PearlWebViewController.h */; };
|
||||
DAFE4A5715039824003ABA7C /* PearlWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE462315039823003ABA7C /* PearlWebViewController.m */; };
|
||||
DAFE4A5815039824003ABA7C /* UIImage_PearlScaling.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A1115039824003ABA7C /* UIImage_PearlScaling.h */; };
|
||||
DAFE4A5915039824003ABA7C /* UIImage_PearlScaling.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */; };
|
||||
DAFE4A5815039824003ABA7C /* UIImage+PearlScaling.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A1115039824003ABA7C /* UIImage+PearlScaling.h */; };
|
||||
DAFE4A5915039824003ABA7C /* UIImage+PearlScaling.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A1215039824003ABA7C /* UIImage+PearlScaling.m */; };
|
||||
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAFE45FA15039823003ABA7C /* Pearl.strings */; };
|
||||
DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */; };
|
||||
DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */; };
|
||||
DAFE4A63150399FF003ABA82 /* UIControl+PearlBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */; };
|
||||
DAFE4A63150399FF003ABA84 /* UIControl+PearlBlocks.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */; };
|
||||
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */; };
|
||||
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */; };
|
||||
DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */; };
|
||||
DAFE4A63150399FF003ABA8C /* UIControl+PearlSelect.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */; };
|
||||
DAFE4A63150399FF003ABA8E /* UIScrollView+PearlFlashingIndicators.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA8D /* UIScrollView+PearlFlashingIndicators.m */; };
|
||||
DAFE4A63150399FF003ABA90 /* UIScrollView+PearlFlashingIndicators.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA8F /* UIScrollView+PearlFlashingIndicators.h */; };
|
||||
DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */; };
|
||||
DAFE4A63150399FF003ABA94 /* NSDateFormatter+RFC3339.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -886,6 +884,23 @@
|
||||
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tip_arrow_wood.png; path = Resources/Tooltips/tip_arrow_wood.png; sourceTree = SOURCE_ROOT; };
|
||||
DA0A1D1315690AF30092735D /* Icon-72@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72@2x.png"; sourceTree = "<group>"; };
|
||||
DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
|
||||
DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
|
||||
DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
|
||||
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
|
||||
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
|
||||
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
|
||||
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Crypto.m"; sourceTree = "<group>"; };
|
||||
DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-UIKit.m"; sourceTree = "<group>"; };
|
||||
DA30E9D515723E6900A68B4C /* PearlLazy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLazy.h; sourceTree = "<group>"; };
|
||||
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
|
||||
DA40C25F1586099D0079CE6E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2601586099D0079CE6E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2621586099E0079CE6E /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2631586099E0079CE6E /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libiCloudStoreManager.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA4425F11557BF260052177D /* UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UbiquityStoreManager.h; sourceTree = "<group>"; };
|
||||
DA4425F21557BF260052177D /* UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UbiquityStoreManager.m; sourceTree = "<group>"; };
|
||||
@@ -905,6 +920,10 @@
|
||||
DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
|
||||
DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios.a"; sourceTree = "<group>"; };
|
||||
DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = "<group>"; };
|
||||
DA829E51159847E0002417D3 /* libFontReplacer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFontReplacer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA829E5E15984812002417D3 /* UIFont+Replacement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+Replacement.h"; sourceTree = "<group>"; };
|
||||
DA829E5F15984812002417D3 /* UIFont+Replacement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+Replacement.m"; sourceTree = "<group>"; };
|
||||
DA902BD01576CA4A00C38161 /* keypad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = keypad.png; sourceTree = "<group>"; };
|
||||
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libInAppSettingsKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsViewController.h; sourceTree = "<group>"; };
|
||||
DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsViewController.m; sourceTree = "<group>"; };
|
||||
@@ -943,6 +962,7 @@
|
||||
DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKPSToggleSwitchSpecifierViewCell.xib; sourceTree = "<group>"; };
|
||||
DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = "<group>"; };
|
||||
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
|
||||
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MasterPassword.xcdatamodel; sourceTree = "<group>"; };
|
||||
DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
DAB8D44115036BCF00CED3BC /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
@@ -965,14 +985,8 @@
|
||||
DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = "<group>"; };
|
||||
DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = "<group>"; };
|
||||
DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; };
|
||||
DAB8D45715036BCF00CED3BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
||||
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
||||
DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
|
||||
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
|
||||
DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
|
||||
DAB8D47315036BF600CED3BC /* ui_box_checked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_box_checked.png; sourceTree = "<group>"; };
|
||||
@@ -1619,8 +1633,14 @@
|
||||
DAC632791486809A0075AEA5 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = External/Pearl/External/jrswizzle/JRSwizzle.h; sourceTree = SOURCE_ROOT; };
|
||||
DAC6327A1486809A0075AEA5 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JRSwizzle.m; path = External/Pearl/External/jrswizzle/JRSwizzle.m; sourceTree = SOURCE_ROOT; };
|
||||
DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
DAC728C8157C247B00889EF2 /* MPPreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPreferencesViewController.h; sourceTree = "<group>"; };
|
||||
DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPreferencesViewController.m; sourceTree = "<group>"; };
|
||||
DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Pearl-Prefix.pch"; sourceTree = "<group>"; };
|
||||
DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_top_right.png; sourceTree = "<group>"; };
|
||||
DACABB8B1572A4A4008BA211 /* tip_basic_black_top_right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_basic_black_top_right@2x.png"; sourceTree = "<group>"; };
|
||||
DACABB8E1572B769008BA211 /* tip_basic_black_top.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_top.png; sourceTree = "<group>"; };
|
||||
DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_basic_black_top@2x.png"; sourceTree = "<group>"; };
|
||||
DAD3125F15528C9C00A3F9ED /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
|
||||
DAD3126015528C9C00A3F9ED /* Crashlytics.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Crashlytics.plist; sourceTree = "<group>"; };
|
||||
DAD3126215528C9C00A3F9ED /* libTestFlight.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libTestFlight.a; sourceTree = "<group>"; };
|
||||
@@ -1642,13 +1662,51 @@
|
||||
DAD312B91552977200A3F9ED /* UIColor+HSV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+HSV.h"; path = "External/Pearl/External/uicolor-utilities/UIColor+HSV.h"; sourceTree = SOURCE_ROOT; };
|
||||
DAD312BA1552977200A3F9ED /* UIColor+HSV.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor+HSV.m"; path = "External/Pearl/External/uicolor-utilities/UIColor+HSV.m"; sourceTree = SOURCE_ROOT; };
|
||||
DAD312C01552A20800A3F9ED /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
|
||||
DAE4C943157E63BE00EFE047 /* avatar-0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-0.png"; sourceTree = "<group>"; };
|
||||
DAE4C944157E63BE00EFE047 /* avatar-0@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-0@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C945157E63BE00EFE047 /* avatar-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-1.png"; sourceTree = "<group>"; };
|
||||
DAE4C946157E63BE00EFE047 /* avatar-1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-1@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C947157E63BE00EFE047 /* avatar-2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-2.png"; sourceTree = "<group>"; };
|
||||
DAE4C948157E63BE00EFE047 /* avatar-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-2@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C949157E63BE00EFE047 /* avatar-3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-3.png"; sourceTree = "<group>"; };
|
||||
DAE4C94A157E63BE00EFE047 /* avatar-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-3@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C94B157E63BE00EFE047 /* avatar-4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-4.png"; sourceTree = "<group>"; };
|
||||
DAE4C94C157E63BE00EFE047 /* avatar-4@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-4@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C94D157E63BE00EFE047 /* avatar-5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-5.png"; sourceTree = "<group>"; };
|
||||
DAE4C94E157E63BE00EFE047 /* avatar-5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-5@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C94F157E63BE00EFE047 /* avatar-6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-6.png"; sourceTree = "<group>"; };
|
||||
DAE4C950157E63BE00EFE047 /* avatar-6@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-6@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C951157E63BE00EFE047 /* avatar-7.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-7.png"; sourceTree = "<group>"; };
|
||||
DAE4C952157E63BE00EFE047 /* avatar-7@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-7@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C953157E63BE00EFE047 /* avatar-8.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-8.png"; sourceTree = "<group>"; };
|
||||
DAE4C954157E63BE00EFE047 /* avatar-8@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-8@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C955157E63BE00EFE047 /* avatar-9.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-9.png"; sourceTree = "<group>"; };
|
||||
DAE4C956157E63BE00EFE047 /* avatar-9@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-9@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C957157E63BE00EFE047 /* avatar-10.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-10.png"; sourceTree = "<group>"; };
|
||||
DAE4C958157E63BE00EFE047 /* avatar-10@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-10@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C959157E63BE00EFE047 /* avatar-11.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-11.png"; sourceTree = "<group>"; };
|
||||
DAE4C95A157E63BE00EFE047 /* avatar-11@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-11@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C95B157E63BE00EFE047 /* avatar-12.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-12.png"; sourceTree = "<group>"; };
|
||||
DAE4C95C157E63BE00EFE047 /* avatar-12@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-12@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C95D157E63BE00EFE047 /* avatar-13.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-13.png"; sourceTree = "<group>"; };
|
||||
DAE4C95E157E63BE00EFE047 /* avatar-13@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-13@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C95F157E63BE00EFE047 /* avatar-14.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-14.png"; sourceTree = "<group>"; };
|
||||
DAE4C960157E63BE00EFE047 /* avatar-14@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-14@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C961157E63BE00EFE047 /* avatar-15.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-15.png"; sourceTree = "<group>"; };
|
||||
DAE4C962157E63BE00EFE047 /* avatar-15@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-15@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C963157E63BE00EFE047 /* avatar-16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-16.png"; sourceTree = "<group>"; };
|
||||
DAE4C964157E63BE00EFE047 /* avatar-16@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-16@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C965157E63BE00EFE047 /* avatar-17.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-17.png"; sourceTree = "<group>"; };
|
||||
DAE4C966157E63BE00EFE047 /* avatar-17@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-17@2x.png"; sourceTree = "<group>"; };
|
||||
DAE4C967157E63BE00EFE047 /* avatar-18.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-18.png"; sourceTree = "<group>"; };
|
||||
DAE4C968157E63BE00EFE047 /* avatar-18@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-18@2x.png"; sourceTree = "<group>"; };
|
||||
DAEBC45214F6364500987BF6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
||||
DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSObject_PearlExport.h; sourceTree = "<group>"; };
|
||||
DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObject_PearlExport.m; sourceTree = "<group>"; };
|
||||
DAFE45DA15039823003ABA7C /* NSString_PearlNSArrayFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSString_PearlNSArrayFormat.h; sourceTree = "<group>"; };
|
||||
DAFE45DB15039823003ABA7C /* NSString_PearlNSArrayFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSString_PearlNSArrayFormat.m; sourceTree = "<group>"; };
|
||||
DAFE45DC15039823003ABA7C /* NSString_PearlSEL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSString_PearlSEL.h; sourceTree = "<group>"; };
|
||||
DAFE45DD15039823003ABA7C /* NSString_PearlSEL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSString_PearlSEL.m; sourceTree = "<group>"; };
|
||||
DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlExport.h"; sourceTree = "<group>"; };
|
||||
DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlExport.m"; sourceTree = "<group>"; };
|
||||
DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PearlNSArrayFormat.h"; sourceTree = "<group>"; };
|
||||
DAFE45DB15039823003ABA7C /* NSString+PearlNSArrayFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+PearlNSArrayFormat.m"; sourceTree = "<group>"; };
|
||||
DAFE45DC15039823003ABA7C /* NSString+PearlSEL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PearlSEL.h"; sourceTree = "<group>"; };
|
||||
DAFE45DD15039823003ABA7C /* NSString+PearlSEL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+PearlSEL.m"; sourceTree = "<group>"; };
|
||||
DAFE45DE15039823003ABA7C /* Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pearl.h; sourceTree = "<group>"; };
|
||||
DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlAbstractStrings.h; sourceTree = "<group>"; };
|
||||
DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlAbstractStrings.m; sourceTree = "<group>"; };
|
||||
@@ -1712,10 +1770,20 @@
|
||||
DAFE462215039823003ABA7C /* PearlWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlWebViewController.h; sourceTree = "<group>"; };
|
||||
DAFE462315039823003ABA7C /* PearlWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlWebViewController.m; sourceTree = "<group>"; };
|
||||
DAFE462415039823003ABA7C /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = "<group>"; };
|
||||
DAFE4A1115039824003ABA7C /* UIImage_PearlScaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIImage_PearlScaling.h; sourceTree = "<group>"; };
|
||||
DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImage_PearlScaling.m; sourceTree = "<group>"; };
|
||||
DAFE4A1115039824003ABA7C /* UIImage+PearlScaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+PearlScaling.h"; sourceTree = "<group>"; };
|
||||
DAFE4A1215039824003ABA7C /* UIImage+PearlScaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+PearlScaling.m"; sourceTree = "<group>"; };
|
||||
DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlAppDelegate.m; sourceTree = "<group>"; };
|
||||
DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlAppDelegate.h; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+PearlBlocks.m"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+PearlBlocks.h"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlKVO.m"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlKVO.h"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+PearlSelect.m"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+PearlSelect.h"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA8D /* UIScrollView+PearlFlashingIndicators.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+PearlFlashingIndicators.m"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA8F /* UIScrollView+PearlFlashingIndicators.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+PearlFlashingIndicators.h"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDateFormatter+RFC3339.m"; sourceTree = "<group>"; };
|
||||
DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDateFormatter+RFC3339.h"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -1731,6 +1799,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */,
|
||||
DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */,
|
||||
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */,
|
||||
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */,
|
||||
@@ -1751,6 +1820,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA829E4E159847E0002417D3 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA95D59914DF063C008D1B94 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1822,6 +1899,7 @@
|
||||
DAD3126115528C9C00A3F9ED /* TestFlight */,
|
||||
DAD3127315528CD200A3F9ED /* Localytics */,
|
||||
DA4425D71557BF260052177D /* iCloudStoreManager */,
|
||||
DA829E5D15984812002417D3 /* FontReplacer */,
|
||||
DA5BFA47147E415C00F98B1E /* Frameworks */,
|
||||
DA5BFA45147E415C00F98B1E /* Products */,
|
||||
);
|
||||
@@ -1837,6 +1915,7 @@
|
||||
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */,
|
||||
DAD3127115528CD200A3F9ED /* libLocalytics.a */,
|
||||
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */,
|
||||
DA829E51159847E0002417D3 /* libFontReplacer.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -1844,6 +1923,7 @@
|
||||
DA5BFA47147E415C00F98B1E /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */,
|
||||
DAD312C01552A20800A3F9ED /* libsqlite3.dylib */,
|
||||
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */,
|
||||
DABB981515100B4000B05417 /* SystemConfiguration.framework */,
|
||||
@@ -1862,8 +1942,17 @@
|
||||
DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */,
|
||||
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */,
|
||||
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */,
|
||||
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */,
|
||||
DA40C2621586099E0079CE6E /* MPElementEntity.h */,
|
||||
DA40C2631586099E0079CE6E /* MPElementEntity.m */,
|
||||
DA40C25F1586099D0079CE6E /* MPUserEntity.h */,
|
||||
DA40C2601586099D0079CE6E /* MPUserEntity.m */,
|
||||
DA0E07941577FE490008A67E /* MPEntities.h */,
|
||||
DA0E07951577FE490008A67E /* MPEntities.m */,
|
||||
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
|
||||
DAB8D43E15036BCF00CED3BC /* iOS */,
|
||||
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
|
||||
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
|
||||
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
|
||||
@@ -1872,14 +1961,9 @@
|
||||
DA4426071557C1990052177D /* MPAppDelegate_Store.m */,
|
||||
DA600C2615056427008E9AB6 /* MPConfig.h */,
|
||||
DA600C2715056427008E9AB6 /* MPConfig.m */,
|
||||
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */,
|
||||
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */,
|
||||
DAB8D45915036BCF00CED3BC /* MPTypes.h */,
|
||||
DAB8D45615036BCF00CED3BC /* MPTypes.m */,
|
||||
DAB8D45715036BCF00CED3BC /* MPElementEntity.h */,
|
||||
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */,
|
||||
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */,
|
||||
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */,
|
||||
DAB8D43E15036BCF00CED3BC /* iOS */,
|
||||
);
|
||||
path = MasterPassword;
|
||||
sourceTree = "<group>";
|
||||
@@ -1894,6 +1978,61 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA829E5D15984812002417D3 /* FontReplacer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA829E5E15984812002417D3 /* UIFont+Replacement.h */,
|
||||
DA829E5F15984812002417D3 /* UIFont+Replacement.m */,
|
||||
);
|
||||
name = FontReplacer;
|
||||
path = "External/FontReplacer/UIFont+Replacement";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA902B931576C0FB00C38161 /* Avatars */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAE4C943157E63BE00EFE047 /* avatar-0.png */,
|
||||
DAE4C944157E63BE00EFE047 /* avatar-0@2x.png */,
|
||||
DAE4C945157E63BE00EFE047 /* avatar-1.png */,
|
||||
DAE4C946157E63BE00EFE047 /* avatar-1@2x.png */,
|
||||
DAE4C947157E63BE00EFE047 /* avatar-2.png */,
|
||||
DAE4C948157E63BE00EFE047 /* avatar-2@2x.png */,
|
||||
DAE4C949157E63BE00EFE047 /* avatar-3.png */,
|
||||
DAE4C94A157E63BE00EFE047 /* avatar-3@2x.png */,
|
||||
DAE4C94B157E63BE00EFE047 /* avatar-4.png */,
|
||||
DAE4C94C157E63BE00EFE047 /* avatar-4@2x.png */,
|
||||
DAE4C94D157E63BE00EFE047 /* avatar-5.png */,
|
||||
DAE4C94E157E63BE00EFE047 /* avatar-5@2x.png */,
|
||||
DAE4C94F157E63BE00EFE047 /* avatar-6.png */,
|
||||
DAE4C950157E63BE00EFE047 /* avatar-6@2x.png */,
|
||||
DAE4C951157E63BE00EFE047 /* avatar-7.png */,
|
||||
DAE4C952157E63BE00EFE047 /* avatar-7@2x.png */,
|
||||
DAE4C953157E63BE00EFE047 /* avatar-8.png */,
|
||||
DAE4C954157E63BE00EFE047 /* avatar-8@2x.png */,
|
||||
DAE4C955157E63BE00EFE047 /* avatar-9.png */,
|
||||
DAE4C956157E63BE00EFE047 /* avatar-9@2x.png */,
|
||||
DAE4C957157E63BE00EFE047 /* avatar-10.png */,
|
||||
DAE4C958157E63BE00EFE047 /* avatar-10@2x.png */,
|
||||
DAE4C959157E63BE00EFE047 /* avatar-11.png */,
|
||||
DAE4C95A157E63BE00EFE047 /* avatar-11@2x.png */,
|
||||
DAE4C95B157E63BE00EFE047 /* avatar-12.png */,
|
||||
DAE4C95C157E63BE00EFE047 /* avatar-12@2x.png */,
|
||||
DAE4C95D157E63BE00EFE047 /* avatar-13.png */,
|
||||
DAE4C95E157E63BE00EFE047 /* avatar-13@2x.png */,
|
||||
DAE4C95F157E63BE00EFE047 /* avatar-14.png */,
|
||||
DAE4C960157E63BE00EFE047 /* avatar-14@2x.png */,
|
||||
DAE4C961157E63BE00EFE047 /* avatar-15.png */,
|
||||
DAE4C962157E63BE00EFE047 /* avatar-15@2x.png */,
|
||||
DAE4C963157E63BE00EFE047 /* avatar-16.png */,
|
||||
DAE4C964157E63BE00EFE047 /* avatar-16@2x.png */,
|
||||
DAE4C965157E63BE00EFE047 /* avatar-17.png */,
|
||||
DAE4C966157E63BE00EFE047 /* avatar-17@2x.png */,
|
||||
DAE4C967157E63BE00EFE047 /* avatar-18.png */,
|
||||
DAE4C968157E63BE00EFE047 /* avatar-18@2x.png */,
|
||||
);
|
||||
path = Avatars;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA95D59E14DF063C008D1B94 /* InAppSettingsKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1986,6 +2125,8 @@
|
||||
DAB8D44B15036BCF00CED3BC /* MPGuideViewController.m */,
|
||||
DAB8D44C15036BCF00CED3BC /* MPMainViewController.h */,
|
||||
DAB8D44D15036BCF00CED3BC /* MPMainViewController.m */,
|
||||
DAC728C8157C247B00889EF2 /* MPPreferencesViewController.h */,
|
||||
DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */,
|
||||
DAB8D44E15036BCF00CED3BC /* MPSearchDelegate.h */,
|
||||
DAB8D44F15036BCF00CED3BC /* MPSearchDelegate.m */,
|
||||
DAB8D45015036BCF00CED3BC /* MPTypeViewController.h */,
|
||||
@@ -2000,6 +2141,8 @@
|
||||
DAB8D46F15036BF600CED3BC /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA902BD01576CA4A00C38161 /* keypad.png */,
|
||||
DA902B931576C0FB00C38161 /* Avatars */,
|
||||
DAB8D47015036BF600CED3BC /* Automaton */,
|
||||
DAB8D4CE15036BF600CED3BC /* Background */,
|
||||
DAB8D4D215036BF600CED3BC /* ciphers.plist */,
|
||||
@@ -2637,12 +2780,10 @@
|
||||
DAB8D6B715036BF600CED3BC /* Tooltips */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA0A1D0715690AD40092735D /* tip_arrow_banana.png */,
|
||||
DA0A1D0815690AD40092735D /* tip_arrow_black.png */,
|
||||
DA0A1D0915690AD40092735D /* tip_arrow_gray.png */,
|
||||
DA0A1D0A15690AD40092735D /* tip_arrow_mercury.png */,
|
||||
DA0A1D0B15690AD40092735D /* tip_arrow_teal.png */,
|
||||
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */,
|
||||
DACABB8E1572B769008BA211 /* tip_basic_black_top.png */,
|
||||
DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */,
|
||||
DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */,
|
||||
DACABB8B1572A4A4008BA211 /* tip_basic_black_top_right@2x.png */,
|
||||
DAB8D6B815036BF600CED3BC /* tip_alert_banana.png */,
|
||||
DAB8D6B915036BF600CED3BC /* tip_alert_banana@2x.png */,
|
||||
DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */,
|
||||
@@ -2655,11 +2796,17 @@
|
||||
DAB8D6C115036BF600CED3BC /* tip_alert_teal@2x.png */,
|
||||
DAB8D6C215036BF600CED3BC /* tip_alert_wood.png */,
|
||||
DAB8D6C315036BF600CED3BC /* tip_alert_wood@2x.png */,
|
||||
DA0A1D0715690AD40092735D /* tip_arrow_banana.png */,
|
||||
DAB8D6C415036BF600CED3BC /* tip_arrow_banana@2x.png */,
|
||||
DA0A1D0815690AD40092735D /* tip_arrow_black.png */,
|
||||
DAB8D6C515036BF600CED3BC /* tip_arrow_black@2x.png */,
|
||||
DA0A1D0915690AD40092735D /* tip_arrow_gray.png */,
|
||||
DAB8D6C615036BF600CED3BC /* tip_arrow_gray@2x.png */,
|
||||
DA0A1D0A15690AD40092735D /* tip_arrow_mercury.png */,
|
||||
DAB8D6C715036BF600CED3BC /* tip_arrow_mercury@2x.png */,
|
||||
DA0A1D0B15690AD40092735D /* tip_arrow_teal.png */,
|
||||
DAB8D6C815036BF600CED3BC /* tip_arrow_teal@2x.png */,
|
||||
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */,
|
||||
DAB8D6C915036BF600CED3BC /* tip_arrow_wood@2x.png */,
|
||||
DAB8D6CA15036BF600CED3BC /* tip_basic_banana.png */,
|
||||
DAB8D6CB15036BF600CED3BC /* tip_basic_banana@2x.png */,
|
||||
@@ -2823,13 +2970,20 @@
|
||||
DAFE45D715039823003ABA7C /* Pearl */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */,
|
||||
DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */,
|
||||
DAFE45DA15039823003ABA7C /* NSString_PearlNSArrayFormat.h */,
|
||||
DAFE45DB15039823003ABA7C /* NSString_PearlNSArrayFormat.m */,
|
||||
DAFE45DC15039823003ABA7C /* NSString_PearlSEL.h */,
|
||||
DAFE45DD15039823003ABA7C /* NSString_PearlSEL.m */,
|
||||
DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */,
|
||||
DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */,
|
||||
DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */,
|
||||
DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */,
|
||||
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */,
|
||||
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */,
|
||||
DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */,
|
||||
DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */,
|
||||
DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */,
|
||||
DAFE45DB15039823003ABA7C /* NSString+PearlNSArrayFormat.m */,
|
||||
DAFE45DC15039823003ABA7C /* NSString+PearlSEL.h */,
|
||||
DAFE45DD15039823003ABA7C /* NSString+PearlSEL.m */,
|
||||
DAFE45DE15039823003ABA7C /* Pearl.h */,
|
||||
DA30E9CD15722ECA00A68B4C /* Pearl.m */,
|
||||
DAFE45DF15039823003ABA7C /* PearlAbstractStrings.h */,
|
||||
DAFE45E015039823003ABA7C /* PearlAbstractStrings.m */,
|
||||
DAFE45E315039823003ABA7C /* PearlCodeUtils.h */,
|
||||
@@ -2840,6 +2994,8 @@
|
||||
DAFE45E815039823003ABA7C /* PearlDeviceUtils.m */,
|
||||
DAFE45E915039823003ABA7C /* PearlInfoPlist.h */,
|
||||
DAFE45EA15039823003ABA7C /* PearlInfoPlist.m */,
|
||||
DA30E9D515723E6900A68B4C /* PearlLazy.h */,
|
||||
DA30E9D615723E6900A68B4C /* PearlLazy.m */,
|
||||
DAFE45EB15039823003ABA7C /* PearlLogger.h */,
|
||||
DAFE45EC15039823003ABA7C /* PearlLogger.m */,
|
||||
DAFE45ED15039823003ABA7C /* PearlMathUtils.h */,
|
||||
@@ -2871,6 +3027,7 @@
|
||||
children = (
|
||||
DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */,
|
||||
DAFE45FD15039823003ABA7C /* Pearl-Crypto.h */,
|
||||
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */,
|
||||
DAFE45FE15039823003ABA7C /* PearlCryptUtils.h */,
|
||||
DAFE45FF15039823003ABA7C /* PearlCryptUtils.m */,
|
||||
DAFE460015039823003ABA7C /* PearlKeyChain.h */,
|
||||
@@ -2888,8 +3045,15 @@
|
||||
DAFE460715039823003ABA7C /* Pearl-UIKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAFE4A63150399FF003ABA8F /* UIScrollView+PearlFlashingIndicators.h */,
|
||||
DAFE4A63150399FF003ABA8D /* UIScrollView+PearlFlashingIndicators.m */,
|
||||
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */,
|
||||
DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */,
|
||||
DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */,
|
||||
DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */,
|
||||
DAFE460815039823003ABA7C /* Pearl-UIKit-Dependencies.h */,
|
||||
DAFE460915039823003ABA7C /* Pearl-UIKit.h */,
|
||||
DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */,
|
||||
DAFE460A15039823003ABA7C /* PearlAlert.h */,
|
||||
DAFE460B15039823003ABA7C /* PearlAlert.m */,
|
||||
DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */,
|
||||
@@ -2920,8 +3084,8 @@
|
||||
DAFE462315039823003ABA7C /* PearlWebViewController.m */,
|
||||
DAFE462415039823003ABA7C /* README */,
|
||||
DAFE462515039823003ABA7C /* Resources */,
|
||||
DAFE4A1115039824003ABA7C /* UIImage_PearlScaling.h */,
|
||||
DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */,
|
||||
DAFE4A1115039824003ABA7C /* UIImage+PearlScaling.h */,
|
||||
DAFE4A1215039824003ABA7C /* UIImage+PearlScaling.m */,
|
||||
);
|
||||
name = "Pearl-UIKit";
|
||||
path = "External/Pearl/Pearl-UIKit";
|
||||
@@ -2945,6 +3109,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA829E4F159847E0002417D3 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA829E6015984813002417D3 /* UIFont+Replacement.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA95D59A14DF063C008D1B94 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -2988,9 +3160,9 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DAFE4A1315039824003ABA7C /* NSObject_PearlExport.h in Headers */,
|
||||
DAFE4A1515039824003ABA7C /* NSString_PearlNSArrayFormat.h in Headers */,
|
||||
DAFE4A1715039824003ABA7C /* NSString_PearlSEL.h in Headers */,
|
||||
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */,
|
||||
DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */,
|
||||
DAFE4A1715039824003ABA7C /* NSString+PearlSEL.h in Headers */,
|
||||
DAFE4A1915039824003ABA7C /* Pearl.h in Headers */,
|
||||
DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */,
|
||||
DAFE4A1E15039824003ABA7C /* PearlCodeUtils.h in Headers */,
|
||||
@@ -3023,8 +3195,15 @@
|
||||
DAFE4A5215039824003ABA7C /* PearlUIUtils.h in Headers */,
|
||||
DAFE4A5415039824003ABA7C /* PearlValidatingTextField.h in Headers */,
|
||||
DAFE4A5615039824003ABA7C /* PearlWebViewController.h in Headers */,
|
||||
DAFE4A5815039824003ABA7C /* UIImage_PearlScaling.h in Headers */,
|
||||
DAFE4A5815039824003ABA7C /* UIImage+PearlScaling.h in Headers */,
|
||||
DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */,
|
||||
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */,
|
||||
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */,
|
||||
DAFE4A63150399FF003ABA84 /* UIControl+PearlBlocks.h in Headers */,
|
||||
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */,
|
||||
DAFE4A63150399FF003ABA8C /* UIControl+PearlSelect.h in Headers */,
|
||||
DAFE4A63150399FF003ABA90 /* UIScrollView+PearlFlashingIndicators.h in Headers */,
|
||||
DAFE4A63150399FF003ABA94 /* NSDateFormatter+RFC3339.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -3079,6 +3258,23 @@
|
||||
productReference = DA5BFA44147E415C00F98B1E /* MasterPassword.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
DA829E50159847E0002417D3 /* FontReplacer */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA829E59159847E0002417D3 /* Build configuration list for PBXNativeTarget "FontReplacer" */;
|
||||
buildPhases = (
|
||||
DA829E4D159847E0002417D3 /* Sources */,
|
||||
DA829E4E159847E0002417D3 /* Frameworks */,
|
||||
DA829E4F159847E0002417D3 /* Headers */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = FontReplacer;
|
||||
productName = FontReplacer;
|
||||
productReference = DA829E51159847E0002417D3 /* libFontReplacer.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
DA95D59B14DF063C008D1B94 /* InAppSettingsKit */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */;
|
||||
@@ -3203,6 +3399,7 @@
|
||||
DA95D59B14DF063C008D1B94 /* InAppSettingsKit */,
|
||||
DAD3127015528CD200A3F9ED /* Localytics */,
|
||||
DA4425CA1557BED40052177D /* iCloudStoreManager */,
|
||||
DA829E50159847E0002417D3 /* FontReplacer */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -3817,86 +4014,60 @@
|
||||
DAB8D93715036BF700CED3BC /* lock_red.png in Resources */,
|
||||
DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */,
|
||||
DAB8D93915036BF700CED3BC /* logo-bare.png in Resources */,
|
||||
DAB8D93A15036BF700CED3BC /* tip_alert_banana.png in Resources */,
|
||||
DAB8D93B15036BF700CED3BC /* tip_alert_banana@2x.png in Resources */,
|
||||
DAB8D93C15036BF700CED3BC /* tip_alert_black.png in Resources */,
|
||||
DAB8D93D15036BF700CED3BC /* tip_alert_black@2x.png in Resources */,
|
||||
DAB8D93E15036BF700CED3BC /* tip_alert_gray.png in Resources */,
|
||||
DAB8D93F15036BF700CED3BC /* tip_alert_gray@2x.png in Resources */,
|
||||
DAB8D94015036BF700CED3BC /* tip_alert_mercury.png in Resources */,
|
||||
DAB8D94115036BF700CED3BC /* tip_alert_mercury@2x.png in Resources */,
|
||||
DAB8D94215036BF700CED3BC /* tip_alert_teal.png in Resources */,
|
||||
DAB8D94315036BF700CED3BC /* tip_alert_teal@2x.png in Resources */,
|
||||
DAB8D94415036BF700CED3BC /* tip_alert_wood.png in Resources */,
|
||||
DAB8D94515036BF700CED3BC /* tip_alert_wood@2x.png in Resources */,
|
||||
DAB8D94615036BF700CED3BC /* tip_arrow_banana@2x.png in Resources */,
|
||||
DAB8D94715036BF700CED3BC /* tip_arrow_black@2x.png in Resources */,
|
||||
DAB8D94815036BF700CED3BC /* tip_arrow_gray@2x.png in Resources */,
|
||||
DAB8D94915036BF700CED3BC /* tip_arrow_mercury@2x.png in Resources */,
|
||||
DAB8D94A15036BF700CED3BC /* tip_arrow_teal@2x.png in Resources */,
|
||||
DAB8D94B15036BF700CED3BC /* tip_arrow_wood@2x.png in Resources */,
|
||||
DAB8D94C15036BF700CED3BC /* tip_basic_banana.png in Resources */,
|
||||
DAB8D94D15036BF700CED3BC /* tip_basic_banana@2x.png in Resources */,
|
||||
DAB8D94E15036BF700CED3BC /* tip_basic_black.png in Resources */,
|
||||
DAB8D94F15036BF700CED3BC /* tip_basic_black@2x.png in Resources */,
|
||||
DAB8D95015036BF700CED3BC /* tip_basic_gray.png in Resources */,
|
||||
DAB8D95115036BF700CED3BC /* tip_basic_gray@2x.png in Resources */,
|
||||
DAB8D95215036BF700CED3BC /* tip_basic_mercury.png in Resources */,
|
||||
DAB8D95315036BF700CED3BC /* tip_basic_mercury@2x.png in Resources */,
|
||||
DAB8D95415036BF700CED3BC /* tip_basic_teal.png in Resources */,
|
||||
DAB8D95515036BF700CED3BC /* tip_basic_teal@2x.png in Resources */,
|
||||
DAB8D95615036BF700CED3BC /* tip_basic_wood.png in Resources */,
|
||||
DAB8D95715036BF700CED3BC /* tip_basic_wood@2x.png in Resources */,
|
||||
DAB8D95815036BF700CED3BC /* tip_download_banana.png in Resources */,
|
||||
DAB8D95915036BF700CED3BC /* tip_download_banana@2x.png in Resources */,
|
||||
DAB8D95A15036BF700CED3BC /* tip_download_black.png in Resources */,
|
||||
DAB8D95B15036BF700CED3BC /* tip_download_black@2x.png in Resources */,
|
||||
DAB8D95C15036BF700CED3BC /* tip_download_gray.png in Resources */,
|
||||
DAB8D95D15036BF700CED3BC /* tip_download_gray@2x.png in Resources */,
|
||||
DAB8D95E15036BF700CED3BC /* tip_download_mercury.png in Resources */,
|
||||
DAB8D95F15036BF700CED3BC /* tip_download_mercury@2x.png in Resources */,
|
||||
DAB8D96015036BF700CED3BC /* tip_download_teal.png in Resources */,
|
||||
DAB8D96115036BF700CED3BC /* tip_download_teal@2x.png in Resources */,
|
||||
DAB8D96215036BF700CED3BC /* tip_download_wood.png in Resources */,
|
||||
DAB8D96315036BF700CED3BC /* tip_download_wood@2x.png in Resources */,
|
||||
DAB8D96415036BF700CED3BC /* tip_large_banana.png in Resources */,
|
||||
DAB8D96515036BF700CED3BC /* tip_large_banana@2x.png in Resources */,
|
||||
DAB8D96615036BF700CED3BC /* tip_large_black.png in Resources */,
|
||||
DAB8D96715036BF700CED3BC /* tip_large_black@2x.png in Resources */,
|
||||
DAB8D96815036BF700CED3BC /* tip_large_gray.png in Resources */,
|
||||
DAB8D96915036BF700CED3BC /* tip_large_gray@2x.png in Resources */,
|
||||
DAB8D96A15036BF700CED3BC /* tip_large_mercury.png in Resources */,
|
||||
DAB8D96B15036BF700CED3BC /* tip_large_mercury@2x.png in Resources */,
|
||||
DAB8D96C15036BF700CED3BC /* tip_large_teal.png in Resources */,
|
||||
DAB8D96D15036BF700CED3BC /* tip_large_teal@2x.png in Resources */,
|
||||
DAB8D96E15036BF700CED3BC /* tip_large_wood.png in Resources */,
|
||||
DAB8D96F15036BF700CED3BC /* tip_large_wood@2x.png in Resources */,
|
||||
DAB8D97015036BF700CED3BC /* tip_location_banana.png in Resources */,
|
||||
DAB8D97115036BF700CED3BC /* tip_location_banana@2x.png in Resources */,
|
||||
DAB8D97215036BF700CED3BC /* tip_location_black.png in Resources */,
|
||||
DAB8D97315036BF700CED3BC /* tip_location_black@2x.png in Resources */,
|
||||
DAB8D97415036BF700CED3BC /* tip_location_gray.png in Resources */,
|
||||
DAB8D97515036BF700CED3BC /* tip_location_gray@2x.png in Resources */,
|
||||
DAB8D97615036BF700CED3BC /* tip_location_mercury.png in Resources */,
|
||||
DAB8D97715036BF700CED3BC /* tip_location_mercury@2x.png in Resources */,
|
||||
DAB8D97815036BF700CED3BC /* tip_location_teal.png in Resources */,
|
||||
DAB8D97915036BF700CED3BC /* tip_location_teal@2x.png in Resources */,
|
||||
DAB8D97A15036BF700CED3BC /* tip_location_wood.png in Resources */,
|
||||
DAB8D97B15036BF700CED3BC /* tip_location_wood@2x.png in Resources */,
|
||||
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */,
|
||||
DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */,
|
||||
DAD3126C15528C9C00A3F9ED /* TestFlight.plist in Resources */,
|
||||
DAD3129015528D1600A3F9ED /* Localytics.plist in Resources */,
|
||||
DA0A1D0515690A9A0092735D /* Default.png in Resources */,
|
||||
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */,
|
||||
DA0A1D0D15690AD40092735D /* tip_arrow_banana.png in Resources */,
|
||||
DA0A1D0E15690AD40092735D /* tip_arrow_black.png in Resources */,
|
||||
DA0A1D0F15690AD40092735D /* tip_arrow_gray.png in Resources */,
|
||||
DA0A1D1015690AD40092735D /* tip_arrow_mercury.png in Resources */,
|
||||
DA0A1D1115690AD40092735D /* tip_arrow_teal.png in Resources */,
|
||||
DA0A1D1215690AD40092735D /* tip_arrow_wood.png in Resources */,
|
||||
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */,
|
||||
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */,
|
||||
DACABB861572A2A7008BA211 /* tip_alert_black.png in Resources */,
|
||||
DACABB871572A2A7008BA211 /* tip_alert_black@2x.png in Resources */,
|
||||
DACABB881572A2A7008BA211 /* tip_basic_black.png in Resources */,
|
||||
DACABB891572A2A7008BA211 /* tip_basic_black@2x.png in Resources */,
|
||||
DACABB8C1572A4A5008BA211 /* tip_basic_black_top_right.png in Resources */,
|
||||
DACABB8D1572A4A5008BA211 /* tip_basic_black_top_right@2x.png in Resources */,
|
||||
DACABB901572B76A008BA211 /* tip_basic_black_top.png in Resources */,
|
||||
DACABB911572B76A008BA211 /* tip_basic_black_top@2x.png in Resources */,
|
||||
DAE4C969157E63BE00EFE047 /* avatar-0.png in Resources */,
|
||||
DAE4C96A157E63BE00EFE047 /* avatar-0@2x.png in Resources */,
|
||||
DAE4C96B157E63BE00EFE047 /* avatar-1.png in Resources */,
|
||||
DAE4C96C157E63BE00EFE047 /* avatar-1@2x.png in Resources */,
|
||||
DAE4C96D157E63BE00EFE047 /* avatar-2.png in Resources */,
|
||||
DAE4C96E157E63BE00EFE047 /* avatar-2@2x.png in Resources */,
|
||||
DAE4C96F157E63BE00EFE047 /* avatar-3.png in Resources */,
|
||||
DAE4C970157E63BE00EFE047 /* avatar-3@2x.png in Resources */,
|
||||
DAE4C971157E63BE00EFE047 /* avatar-4.png in Resources */,
|
||||
DAE4C972157E63BE00EFE047 /* avatar-4@2x.png in Resources */,
|
||||
DAE4C973157E63BE00EFE047 /* avatar-5.png in Resources */,
|
||||
DAE4C974157E63BE00EFE047 /* avatar-5@2x.png in Resources */,
|
||||
DAE4C975157E63BE00EFE047 /* avatar-6.png in Resources */,
|
||||
DAE4C976157E63BE00EFE047 /* avatar-6@2x.png in Resources */,
|
||||
DAE4C977157E63BE00EFE047 /* avatar-7.png in Resources */,
|
||||
DAE4C978157E63BE00EFE047 /* avatar-7@2x.png in Resources */,
|
||||
DAE4C979157E63BE00EFE047 /* avatar-8.png in Resources */,
|
||||
DAE4C97A157E63BE00EFE047 /* avatar-8@2x.png in Resources */,
|
||||
DAE4C97B157E63BE00EFE047 /* avatar-9.png in Resources */,
|
||||
DAE4C97C157E63BE00EFE047 /* avatar-9@2x.png in Resources */,
|
||||
DAE4C97D157E63BE00EFE047 /* avatar-10.png in Resources */,
|
||||
DAE4C97E157E63BE00EFE047 /* avatar-10@2x.png in Resources */,
|
||||
DAE4C97F157E63BE00EFE047 /* avatar-11.png in Resources */,
|
||||
DAE4C980157E63BE00EFE047 /* avatar-11@2x.png in Resources */,
|
||||
DAE4C981157E63BE00EFE047 /* avatar-12.png in Resources */,
|
||||
DAE4C982157E63BE00EFE047 /* avatar-12@2x.png in Resources */,
|
||||
DAE4C983157E63BE00EFE047 /* avatar-13.png in Resources */,
|
||||
DAE4C984157E63BE00EFE047 /* avatar-13@2x.png in Resources */,
|
||||
DAE4C985157E63BE00EFE047 /* avatar-14.png in Resources */,
|
||||
DAE4C986157E63BE00EFE047 /* avatar-14@2x.png in Resources */,
|
||||
DAE4C987157E63BE00EFE047 /* avatar-15.png in Resources */,
|
||||
DAE4C988157E63BE00EFE047 /* avatar-15@2x.png in Resources */,
|
||||
DAE4C989157E63BE00EFE047 /* avatar-16.png in Resources */,
|
||||
DAE4C98A157E63BE00EFE047 /* avatar-16@2x.png in Resources */,
|
||||
DAE4C98B157E63BE00EFE047 /* avatar-17.png in Resources */,
|
||||
DAE4C98C157E63BE00EFE047 /* avatar-17@2x.png in Resources */,
|
||||
DAE4C98D157E63BE00EFE047 /* avatar-18.png in Resources */,
|
||||
DAE4C98E157E63BE00EFE047 /* avatar-18@2x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -3915,7 +4086,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/bin/bash -e";
|
||||
shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --tags --always --dirty --long)\nbuild=${description%-g*} build=${build//-/.}\ntag=${description%%-*}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
|
||||
shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --always --dirty --long)\nbuild=${description%-g*} build=${build//-/.}\ntag=${description%%-*}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */ = {
|
||||
@@ -3957,14 +4128,25 @@
|
||||
DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
|
||||
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
|
||||
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
|
||||
DAB8D46B15036BCF00CED3BC /* MPElementStoredEntity.m in Sources */,
|
||||
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
|
||||
DAB8D46D15036BCF00CED3BC /* MPElementEntity.m in Sources */,
|
||||
DAB8D46E15036BCF00CED3BC /* MPElementGeneratedEntity.m in Sources */,
|
||||
DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
|
||||
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
|
||||
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
|
||||
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
|
||||
DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
|
||||
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
|
||||
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */,
|
||||
DA40C2641586099E0079CE6E /* MPElementEntity.m in Sources */,
|
||||
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */,
|
||||
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA829E4D159847E0002417D3 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA829E6115984813002417D3 /* UIFont+Replacement.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -4011,9 +4193,9 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DAFE4A1415039824003ABA7C /* NSObject_PearlExport.m in Sources */,
|
||||
DAFE4A1615039824003ABA7C /* NSString_PearlNSArrayFormat.m in Sources */,
|
||||
DAFE4A1815039824003ABA7C /* NSString_PearlSEL.m in Sources */,
|
||||
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */,
|
||||
DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */,
|
||||
DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */,
|
||||
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */,
|
||||
DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */,
|
||||
DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */,
|
||||
@@ -4041,8 +4223,18 @@
|
||||
DAFE4A5315039824003ABA7C /* PearlUIUtils.m in Sources */,
|
||||
DAFE4A5515039824003ABA7C /* PearlValidatingTextField.m in Sources */,
|
||||
DAFE4A5715039824003ABA7C /* PearlWebViewController.m in Sources */,
|
||||
DAFE4A5915039824003ABA7C /* UIImage_PearlScaling.m in Sources */,
|
||||
DAFE4A5915039824003ABA7C /* UIImage+PearlScaling.m in Sources */,
|
||||
DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */,
|
||||
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */,
|
||||
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */,
|
||||
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */,
|
||||
DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */,
|
||||
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */,
|
||||
DAFE4A63150399FF003ABA82 /* UIControl+PearlBlocks.m in Sources */,
|
||||
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */,
|
||||
DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */,
|
||||
DAFE4A63150399FF003ABA8E /* UIScrollView+PearlFlashingIndicators.m in Sources */,
|
||||
DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -4129,7 +4321,8 @@
|
||||
CLANG_WARN_OBJCPP_ARC_ABI = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Maarten Billemont (DWGU95U4ZD)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -4169,7 +4362,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "FCADE039-9917-4715-9172-ABBAC9806763";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -4187,7 +4380,8 @@
|
||||
CLANG_WARN_OBJCPP_ARC_ABI = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -4226,10 +4420,11 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "1CC091A5-5890-4382-A72F-1397B66FE136";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = AdHoc;
|
||||
};
|
||||
@@ -4277,6 +4472,27 @@
|
||||
};
|
||||
name = AdHoc;
|
||||
};
|
||||
DA829E5A159847E0002417D3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DA829E5B159847E0002417D3 /* AdHoc */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||
};
|
||||
name = AdHoc;
|
||||
};
|
||||
DA829E5C159847E0002417D3 /* AppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||
};
|
||||
name = AppStore;
|
||||
};
|
||||
DA95D5A514DF063C008D1B94 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -4310,7 +4526,8 @@
|
||||
CLANG_WARN_OBJCPP_ARC_ABI = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -4349,10 +4566,11 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "7BF77F10-5D46-4762-B27F-55A39E089052";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = AppStore;
|
||||
};
|
||||
@@ -4569,6 +4787,16 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = AdHoc;
|
||||
};
|
||||
DA829E59159847E0002417D3 /* Build configuration list for PBXNativeTarget "FontReplacer" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DA829E5A159847E0002417D3 /* Debug */,
|
||||
DA829E5B159847E0002417D3 /* AdHoc */,
|
||||
DA829E5C159847E0002417D3 /* AppStore */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = AdHoc;
|
||||
};
|
||||
DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@@ -63,14 +63,23 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Production"
|
||||
buildConfiguration = "AppStore"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Production"
|
||||
buildConfiguration = "AppStore"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "AppStore"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
@@ -57,12 +57,6 @@
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-com.apple.coredata.ubiquity.logLevel 3"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
@@ -75,7 +69,7 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "AdHoc"
|
||||
buildConfiguration = "Debug"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
|
||||
BIN
MasterPassword.sketch/QuickLook/Preview.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 43 KiB |
BIN
MasterPassword.sketch/QuickLook/Thumbnail.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
@@ -10,13 +10,10 @@
|
||||
|
||||
@interface MPAppDelegate_Shared (Key)
|
||||
|
||||
- (void)loadStoredKey;
|
||||
- (IBAction)signOut:(id)sender;
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
|
||||
- (void)signOut;
|
||||
|
||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword;
|
||||
- (void)updateKey:(NSData *)key;
|
||||
- (void)forgetKey;
|
||||
|
||||
- (NSData *)keyWithLength:(NSUInteger)keyLength;
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user;
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,153 +6,169 @@
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPConfig.h"
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
@implementation MPAppDelegate_Shared (Key)
|
||||
|
||||
static NSDictionary *keyQuery() {
|
||||
|
||||
static NSDictionary *MPKeyQuery = nil;
|
||||
if (!MPKeyQuery)
|
||||
MPKeyQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"Saved Master Password", (__bridge id)kSecAttrService,
|
||||
@"default", (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
|
||||
return MPKeyQuery;
|
||||
static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"Saved Master Password", (__bridge id)kSecAttrService,
|
||||
user.name, (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
static NSDictionary *keyIDQuery() {
|
||||
|
||||
static NSDictionary *MPKeyIDQuery = nil;
|
||||
if (!MPKeyIDQuery)
|
||||
MPKeyIDQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"Master Password Check", (__bridge id)kSecAttrService,
|
||||
@"default", (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
|
||||
return MPKeyIDQuery;
|
||||
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
if (key)
|
||||
inf(@"Found key in keychain for: %@", user.userID);
|
||||
|
||||
else {
|
||||
user.saveKey = NO;
|
||||
inf(@"No key found in keychain for: %@", user.userID);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
- (void)forgetKey {
|
||||
|
||||
inf(@"Deleting key and ID from keychain.");
|
||||
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
|
||||
inf(@"Removed key from keychain.");
|
||||
if ([PearlKeyChain deleteItemForQuery:keyIDQuery()] != errSecItemNotFound)
|
||||
inf(@"Removed key ID from keychain.");
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
|
||||
#endif
|
||||
}
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
- (IBAction)signOut:(id)sender {
|
||||
if (user.saveKey) {
|
||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
|
||||
[MPConfig get].saveKey = [NSNumber numberWithBool:NO];
|
||||
[self updateKey:nil];
|
||||
}
|
||||
if (![existingKey isEqualToData:self.key]) {
|
||||
inf(@"Saving key in keychain for: %@", user.userID);
|
||||
|
||||
- (void)loadStoredKey {
|
||||
|
||||
if ([[MPConfig get].saveKey boolValue]) {
|
||||
// Key is stored in keychain. Load it.
|
||||
[self updateKey:[PearlKeyChain dataOfItemForQuery:keyQuery()]];
|
||||
inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing");
|
||||
} else {
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
|
||||
inf(@"Removed key from keychain.");
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
|
||||
#endif
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword {
|
||||
|
||||
if (![tryPassword length])
|
||||
return NO;
|
||||
|
||||
NSData *tryKey = keyForPassword(tryPassword);
|
||||
NSData *tryKeyID = keyIDForKey(tryKey);
|
||||
NSData *keyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
|
||||
inf(@"Key ID known? %@.", keyID? @"YES": @"NO");
|
||||
if (keyID)
|
||||
// A key ID is known -> a password is set.
|
||||
// Make sure the user's entered password matches it.
|
||||
if (![keyID isEqual:tryKeyID]) {
|
||||
wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [keyID encodeHex], [tryKeyID encodeHex]);
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
|
||||
if (result == noErr || result == errSecItemNotFound) {
|
||||
user.saveKey = NO;
|
||||
|
||||
if (result == noErr) {
|
||||
inf(@"Removed key from keychain for: %@", user.userID);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointForgetSavedKey];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)signOut {
|
||||
|
||||
if (self.key)
|
||||
self.key = nil;
|
||||
|
||||
if (self.activeUser) {
|
||||
self.activeUser = nil;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
|
||||
|
||||
NSData *tryKey = nil;
|
||||
|
||||
// Method 1: When the user has no keyID set, set a new key from the given master password.
|
||||
if (!user.keyID) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name))) {
|
||||
user.keyID = keyIDForKey(tryKey);
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
|
||||
if (!user.saveKey)
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
[self forgetSavedKeyFor:user];
|
||||
|
||||
else
|
||||
if (!tryKey) {
|
||||
// Key should be saved in keychain. Load it.
|
||||
if ((tryKey = [self loadSavedKeyFor:user]))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
|
||||
inf(@"Saved password doesn't match keyID for: %@", user.userID);
|
||||
|
||||
tryKey = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Check the given master password string.
|
||||
if (!tryKey) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name)))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
|
||||
|
||||
tryKey = nil;
|
||||
}
|
||||
}
|
||||
|
||||
// No more methods left, fail if key still not known.
|
||||
if (!tryKey) {
|
||||
if (password) {
|
||||
inf(@"Login failed for: %@", user.userID);
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
|
||||
#endif
|
||||
return NO;
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed attributes:nil];
|
||||
}
|
||||
|
||||
|
||||
return NO;
|
||||
}
|
||||
inf(@"Logged in: %@", user.userID);
|
||||
|
||||
if (![self.key isEqualToData:tryKey]) {
|
||||
self.key = tryKey;
|
||||
[self storeSavedKeyFor:user];
|
||||
}
|
||||
|
||||
@try {
|
||||
if ([[MPiOSConfig get].sendInfo boolValue]) {
|
||||
[TestFlight addCustomEnvironmentInformation:user.userID forKey:@"username"];
|
||||
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"While setting username: %@", exception);
|
||||
}
|
||||
|
||||
|
||||
user.lastUsed = [NSDate date];
|
||||
self.activeUser = user;
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered];
|
||||
[TestFlight passCheckpoint:MPCheckpointSignedIn];
|
||||
#endif
|
||||
|
||||
[self updateKey:tryKey];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignedIn
|
||||
attributes:nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateKey:(NSData *)key {
|
||||
|
||||
if (self.key != key) {
|
||||
self.key = key;
|
||||
|
||||
if (key)
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
|
||||
else
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
|
||||
}
|
||||
|
||||
if (self.key) {
|
||||
self.keyID = keyIDForKey(self.key);
|
||||
|
||||
NSData *existingKeyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
|
||||
if (![existingKeyID isEqualToData:self.keyID]) {
|
||||
inf(@"Updating key ID in keychain.");
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyIDQuery()
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.keyID, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
}
|
||||
if ([[MPConfig get].saveKey boolValue]) {
|
||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery()];
|
||||
if (![existingKey isEqualToData:self.key]) {
|
||||
inf(@"Updating key in keychain.");
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery()
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)keyWithLength:(NSUInteger)keyLength {
|
||||
|
||||
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPEntities.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
@interface MPAppDelegate_Shared : PearlAppDelegate
|
||||
#else
|
||||
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
|
||||
#endif
|
||||
|
||||
@property (strong, nonatomic) NSData *key;
|
||||
@property (strong, nonatomic) NSData *keyID;
|
||||
@property (strong, nonatomic) MPUserEntity *activeUser;
|
||||
@property (strong, nonatomic) NSData *key;
|
||||
|
||||
+ (MPAppDelegate_Shared *)get;
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
@implementation MPAppDelegate_Shared
|
||||
|
||||
@synthesize key;
|
||||
@synthesize keyID;
|
||||
@synthesize activeUser;
|
||||
|
||||
+ (MPAppDelegate_Shared *)get {
|
||||
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
return (MPAppDelegate_Shared *)[UIApplication sharedApplication].delegate;
|
||||
#elif defined (__MAC_OS_X_VERSION_MIN_REQUIRED)
|
||||
|
||||
@@ -18,7 +18,7 @@ typedef enum {
|
||||
MPImportResultInternalError,
|
||||
} MPImportResult;
|
||||
|
||||
@interface MPAppDelegate_Shared (Store) <UbiquityStoreManagerDelegate>
|
||||
@interface MPAppDelegate_Shared (Store)<UbiquityStoreManagerDelegate>
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext;
|
||||
+ (NSManagedObjectModel *)managedObjectModel;
|
||||
@@ -27,7 +27,6 @@ typedef enum {
|
||||
|
||||
- (UbiquityStoreManager *)storeManager;
|
||||
- (void)saveContext;
|
||||
- (void)printStore;
|
||||
|
||||
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
|
||||
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation;
|
||||
|
||||
@@ -7,80 +7,75 @@
|
||||
//
|
||||
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPConfig.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
@implementation MPAppDelegate_Shared (Store)
|
||||
|
||||
static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
|
||||
#pragma mark - Core Data setup
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext {
|
||||
|
||||
|
||||
return [[self get] managedObjectContext];
|
||||
}
|
||||
|
||||
+ (NSManagedObjectModel *)managedObjectModel {
|
||||
|
||||
|
||||
return [[self get] managedObjectModel];
|
||||
}
|
||||
|
||||
- (NSManagedObjectModel *)managedObjectModel {
|
||||
|
||||
|
||||
static NSManagedObjectModel *managedObjectModel = nil;
|
||||
if (managedObjectModel)
|
||||
return managedObjectModel;
|
||||
|
||||
|
||||
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"];
|
||||
return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
|
||||
}
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContext {
|
||||
|
||||
|
||||
static NSManagedObjectContext *managedObjectContext = nil;
|
||||
if (managedObjectContext)
|
||||
return managedObjectContext;
|
||||
|
||||
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
|
||||
assert(coordinator);
|
||||
|
||||
|
||||
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
[managedObjectContext performBlockAndWait:^{
|
||||
managedObjectContext.persistentStoreCoordinator = coordinator;
|
||||
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
||||
managedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
|
||||
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
||||
}];
|
||||
|
||||
|
||||
return managedObjectContext;
|
||||
}
|
||||
|
||||
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
|
||||
|
||||
|
||||
// Start loading the store.
|
||||
[self storeManager];
|
||||
|
||||
// Wait until the storeManager is ready.
|
||||
for(__block BOOL isReady = [self storeManager].isReady; !isReady;)
|
||||
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
isReady = [self storeManager].isReady;
|
||||
});
|
||||
|
||||
assert([self storeManager].isReady);
|
||||
while (![self storeManager].isReady)
|
||||
[NSThread sleepForTimeInterval:0.1];
|
||||
|
||||
return [self storeManager].persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
- (UbiquityStoreManager *)storeManager {
|
||||
|
||||
|
||||
static UbiquityStoreManager *storeManager = nil;
|
||||
if (storeManager)
|
||||
return storeManager;
|
||||
|
||||
|
||||
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
|
||||
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
|
||||
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
|
||||
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
|
||||
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
|
||||
#if TARGET_OS_IPHONE
|
||||
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSPersistentStoreFileProtectionKey]
|
||||
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
|
||||
forKey:NSPersistentStoreFileProtectionKey]
|
||||
#else
|
||||
additionalStoreOptions:nil
|
||||
#endif
|
||||
];
|
||||
];
|
||||
storeManager.delegate = self;
|
||||
#ifdef DEBUG
|
||||
storeManager.hardResetEnabled = YES;
|
||||
@@ -88,9 +83,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
|
||||
object:[UIApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[storeManager checkiCloudStatus];
|
||||
}];
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[storeManager checkiCloudStatus];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
|
||||
object:[NSApplication sharedApplication] queue:nil
|
||||
@@ -101,9 +96,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
|
||||
object:[UIApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self saveContext];
|
||||
}];
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self saveContext];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
|
||||
object:[NSApplication sharedApplication] queue:nil
|
||||
@@ -111,97 +106,56 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
[self saveContext];
|
||||
}];
|
||||
#endif
|
||||
|
||||
|
||||
return storeManager;
|
||||
}
|
||||
|
||||
- (void)saveContext {
|
||||
|
||||
|
||||
[self.managedObjectContext performBlock:^{
|
||||
NSError *error = nil;
|
||||
if ([self.managedObjectContext hasChanges])
|
||||
if (![self.managedObjectContext save:&error])
|
||||
err(@"While saving context: %@", error);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)printStore {
|
||||
|
||||
if (![self managedObjectModel] || ![self managedObjectContext]) {
|
||||
trc(@"Not printing store: store not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
[self.managedObjectContext performBlock:^{
|
||||
trc(@"=== All entities ===");
|
||||
for(NSEntityDescription *entity in [[self managedObjectModel] entities]) {
|
||||
NSFetchRequest *request = [NSFetchRequest new];
|
||||
[request setEntity:entity];
|
||||
NSError *error;
|
||||
NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:&error];
|
||||
for(NSManagedObject *o in results) {
|
||||
if ([o isKindOfClass:[MPElementEntity class]]) {
|
||||
MPElementEntity *e = (MPElementEntity *)o;
|
||||
trc(@"For descriptor: %@, found: %@: %@ (%@)", entity.name, [o class], e.name, e.keyID);
|
||||
} else {
|
||||
trc(@"For descriptor: %@, found: %@", entity.name, [o class]);
|
||||
}
|
||||
}
|
||||
}
|
||||
trc(@"---");
|
||||
if ([MPAppDelegate_Shared get].keyID) {
|
||||
trc(@"=== Known sites ===");
|
||||
NSFetchRequest *fetchRequest = [[self managedObjectModel]
|
||||
fetchRequestFromTemplateWithName:@"MPElements"
|
||||
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"", @"query",
|
||||
[MPAppDelegate_Shared get].keyID, @"keyID",
|
||||
nil]];
|
||||
[fetchRequest setSortDescriptors:
|
||||
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
|
||||
|
||||
NSError *error = nil;
|
||||
for (MPElementEntity *e in [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error]) {
|
||||
trc(@"Found site: %@ (%@): %@", e.name, e.keyID, e);
|
||||
}
|
||||
trc(@"---");
|
||||
} else
|
||||
trc(@"Not printing sites: master password not set.");
|
||||
err(@"While saving context: %@", error);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - UbiquityStoreManagerDelegate
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm {
|
||||
|
||||
|
||||
return self.managedObjectContext;
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
|
||||
|
||||
|
||||
dbg(@"[StoreManager] %@", message);
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
|
||||
|
||||
// manager.iCloudEnabled is more reliable (eg. iOS tampers with didSwitch a bit)
|
||||
|
||||
// manager.iCloudEnabled is more reliable (eg. iOS' MPAppDelegate tampers with didSwitch a bit)
|
||||
iCloudEnabled = manager.iCloudEnabled;
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:iCloudEnabled? MPTestFlightCheckpointCloudEnabled: MPTestFlightCheckpointCloudDisabled];
|
||||
#endif
|
||||
|
||||
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
|
||||
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
|
||||
|
||||
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause context:(id)context {
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:PearlString(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)];
|
||||
#endif
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause
|
||||
context:(id)context {
|
||||
|
||||
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
|
||||
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:PearlString(@"MPCheckpointMPErrorUbiquity_%d", cause)];
|
||||
#endif
|
||||
|
||||
switch (cause) {
|
||||
case UbiquityStoreManagerErrorCauseDeleteStore:
|
||||
case UbiquityStoreManagerErrorCauseDeleteLogs:
|
||||
@@ -209,21 +163,27 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
case UbiquityStoreManagerErrorCauseClearStore:
|
||||
break;
|
||||
case UbiquityStoreManagerErrorCauseOpenLocalStore: {
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointLocalStoreIncompatible];
|
||||
#endif
|
||||
wrn(@"Local store could not be opened, resetting it.");
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible
|
||||
attributes:nil];
|
||||
manager.hardResetEnabled = YES;
|
||||
[manager hardResetLocalStorage];
|
||||
|
||||
|
||||
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."];
|
||||
return;
|
||||
}
|
||||
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCloudStoreIncompatible];
|
||||
#endif
|
||||
wrn(@"iCloud store could not be opened, resetting it.");
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible
|
||||
attributes:nil];
|
||||
manager.hardResetEnabled = YES;
|
||||
[manager hardResetCloudStorage];
|
||||
break;
|
||||
@@ -233,49 +193,39 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
|
||||
#pragma mark - Import / Export
|
||||
|
||||
- (void)loadRFC3339DateFormatter {
|
||||
|
||||
if (rfc3339DateFormatter)
|
||||
return;
|
||||
|
||||
rfc3339DateFormatter = [NSDateFormatter new];
|
||||
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
|
||||
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
|
||||
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
||||
}
|
||||
|
||||
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
|
||||
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
|
||||
|
||||
[self loadRFC3339DateFormatter];
|
||||
|
||||
|
||||
inf(@"Importing sites.");
|
||||
|
||||
static NSRegularExpression *headerPattern, *sitePattern;
|
||||
__autoreleasing NSError *error;
|
||||
if (!headerPattern) {
|
||||
headerPattern = [[NSRegularExpression alloc]
|
||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:0 error:&error];
|
||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:0 error:&error];
|
||||
if (error)
|
||||
err(@"Error loading the header pattern: %@", error);
|
||||
err(@"Error loading the header pattern: %@", error);
|
||||
}
|
||||
if (!sitePattern) {
|
||||
sitePattern = [[NSRegularExpression alloc]
|
||||
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
|
||||
options:0 error:&error];
|
||||
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
|
||||
options:0 error:&error];
|
||||
if (error)
|
||||
err(@"Error loading the site pattern: %@", error);
|
||||
err(@"Error loading the site pattern: %@", error);
|
||||
}
|
||||
if (!headerPattern || !sitePattern)
|
||||
return MPImportResultInternalError;
|
||||
|
||||
NSString *keyIDHex = nil;
|
||||
BOOL headerStarted = NO, headerEnded = NO;
|
||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
||||
|
||||
NSData *key = nil;
|
||||
NSString *keyIDHex = nil, *userName = nil;
|
||||
MPUserEntity *user = nil;
|
||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
||||
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
for(NSString *importedSiteLine in importedSiteLines) {
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
for (NSString *importedSiteLine in importedSiteLines) {
|
||||
if ([importedSiteLine hasPrefix:@"#"]) {
|
||||
// Comment or header
|
||||
if (!headerStarted) {
|
||||
@@ -289,97 +239,131 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
headerEnded = YES;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Header
|
||||
if ([headerPattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
|
||||
err(@"Invalid header format in line: %@", importedSiteLine);
|
||||
return MPImportResultMalformedInput;
|
||||
}
|
||||
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject];
|
||||
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
if ([key isEqualToString:@"Key ID"]) {
|
||||
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]])
|
||||
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
|
||||
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
|
||||
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
if ([headerName isEqualToString:@"User Name"]) {
|
||||
userName = headerValue;
|
||||
key = keyForPassword(password, userName);
|
||||
|
||||
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
|
||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
|
||||
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
|
||||
}
|
||||
if ([headerName isEqualToString:@"Key ID"]) {
|
||||
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]])
|
||||
return MPImportResultInvalidPassword;
|
||||
}
|
||||
|
||||
if ([headerName isEqualToString:@"Passwords"]) {
|
||||
if ([headerValue isEqualToString:@"VISIBLE"])
|
||||
clearText = YES;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
if (!headerEnded)
|
||||
continue;
|
||||
if (!keyIDHex)
|
||||
if (!keyIDHex || ![userName length])
|
||||
return MPImportResultMalformedInput;
|
||||
if (![importedSiteLine length])
|
||||
continue;
|
||||
|
||||
|
||||
// Site
|
||||
if ([sitePattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
|
||||
err(@"Invalid site format in line: %@", importedSiteLine);
|
||||
return MPImportResultMalformedInput;
|
||||
}
|
||||
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject];
|
||||
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
|
||||
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
|
||||
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
|
||||
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
||||
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
||||
|
||||
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:0
|
||||
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
|
||||
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
|
||||
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
|
||||
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
|
||||
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
||||
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
||||
|
||||
// Find existing site.
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND keyID == %@", name, keyIDHex];
|
||||
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||
if (error)
|
||||
if (user) {
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
|
||||
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||
if (error)
|
||||
err(@"Couldn't search existing sites: %@", error);
|
||||
if (!existingSites)
|
||||
return MPImportResultInternalError;
|
||||
|
||||
[elementsToDelete addObjectsFromArray:existingSites];
|
||||
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
|
||||
if (!existingSites) {
|
||||
err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID);
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
|
||||
[elementsToDelete addObjectsFromArray:existingSites];
|
||||
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inf(@"Importing %u sites, deleting %u sites, for user: %@",
|
||||
[importedSiteElements count], [elementsToDelete count], [MPUserEntity idFor:userName]);
|
||||
|
||||
// Ask for confirmation to import these sites.
|
||||
if (!confirmation([importedSiteElements count], [elementsToDelete count]))
|
||||
if (!confirmation([importedSiteElements count], [elementsToDelete count])) {
|
||||
inf(@"Import cancelled.");
|
||||
return MPImportResultCancelled;
|
||||
|
||||
}
|
||||
|
||||
// Delete existing sites.
|
||||
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
||||
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
|
||||
[self.managedObjectContext deleteObject:obj];
|
||||
}];
|
||||
[self saveContext];
|
||||
|
||||
|
||||
// Import new sites.
|
||||
if (!user) {
|
||||
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
user.name = userName;
|
||||
user.keyID = [keyIDHex decodeHex];
|
||||
}
|
||||
for (NSArray *siteElements in importedSiteElements) {
|
||||
NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]];
|
||||
NSInteger uses = [[siteElements objectAtIndex:1] integerValue];
|
||||
MPElementType type = (unsigned)[[siteElements objectAtIndex:2] integerValue];
|
||||
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
|
||||
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
|
||||
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
|
||||
NSString *name = [siteElements objectAtIndex:3];
|
||||
NSString *exportContent = [siteElements objectAtIndex:4];
|
||||
|
||||
|
||||
// Create new site.
|
||||
inf(@"Importing site: name=%@, lastUsed=%@, uses=%d, type=%u, keyID=%@", name, lastUsed, uses, type, keyIDHex);
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
element.name = name;
|
||||
element.keyID = [keyIDHex decodeHex];
|
||||
element.type = type;
|
||||
element.uses = uses;
|
||||
element.lastUsed = [lastUsed timeIntervalSinceReferenceDate];
|
||||
element.name = name;
|
||||
element.user = user;
|
||||
element.type = type;
|
||||
element.uses = uses;
|
||||
element.lastUsed = lastUsed;
|
||||
if ([exportContent length])
|
||||
[element importContent:exportContent];
|
||||
if (clearText)
|
||||
[element importClearTextContent:exportContent usingKey:key];
|
||||
else
|
||||
[element importProtectedContent:exportContent];
|
||||
}
|
||||
[self saveContext];
|
||||
|
||||
|
||||
inf(@"Import completed successfully.");
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported];
|
||||
[TestFlight passCheckpoint:MPCheckpointSitesImported];
|
||||
#endif
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported
|
||||
attributes:nil];
|
||||
|
||||
return MPImportResultSuccess;
|
||||
}
|
||||
|
||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
|
||||
|
||||
[self loadRFC3339DateFormatter];
|
||||
|
||||
|
||||
inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", self.activeUser.userID);
|
||||
|
||||
// Header.
|
||||
NSMutableString *export = [NSMutableString new];
|
||||
[export appendFormat:@"# Master Password site export\n"];
|
||||
@@ -390,8 +374,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
[export appendFormat:@"# \n"];
|
||||
[export appendFormat:@"##\n"];
|
||||
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
|
||||
[export appendFormat:@"# Key ID: %@\n", [self.keyID encodeHex]];
|
||||
[export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]];
|
||||
[export appendFormat:@"# User Name: %@\n", self.activeUser.name];
|
||||
[export appendFormat:@"# Key ID: %@\n", [self.activeUser.keyID encodeHex]];
|
||||
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
|
||||
if (showPasswords)
|
||||
[export appendFormat:@"# Passwords: VISIBLE\n"];
|
||||
else
|
||||
@@ -400,39 +385,34 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
[export appendFormat:@"#\n"];
|
||||
[export appendFormat:@"# Last Times Password Site\tSite\n"];
|
||||
[export appendFormat:@"# used used type name\tpassword\n"];
|
||||
|
||||
|
||||
// Sites.
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", self.keyID];
|
||||
__autoreleasing NSError *error = nil;
|
||||
NSArray *elements = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||
if (error)
|
||||
err(@"Error fetching sites for export: %@", error);
|
||||
|
||||
for (MPElementEntity *element in elements) {
|
||||
NSTimeInterval lastUsed = element.lastUsed;
|
||||
int16_t uses = element.uses;
|
||||
MPElementType type = (unsigned)element.type;
|
||||
NSString *name = element.name;
|
||||
for (MPElementEntity *element in self.activeUser.elements) {
|
||||
NSDate *lastUsed = element.lastUsed;
|
||||
NSUInteger uses = element.uses;
|
||||
MPElementType type = element.type;
|
||||
NSString *name = element.name;
|
||||
NSString *content = nil;
|
||||
|
||||
|
||||
// Determine the content to export.
|
||||
if (!(type & MPElementFeatureDevicePrivate)) {
|
||||
if (showPasswords)
|
||||
content = element.content;
|
||||
else if (type & MPElementFeatureExportContent)
|
||||
content = element.exportContent;
|
||||
else
|
||||
if (type & MPElementFeatureExportContent)
|
||||
content = element.exportContent;
|
||||
}
|
||||
|
||||
|
||||
[export appendFormat:@"%@ %8d %8d %20s\t%@\n",
|
||||
[rfc3339DateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:lastUsed]], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content? content: @""];
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
|
||||
? content: @""];
|
||||
}
|
||||
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported];
|
||||
[TestFlight passCheckpoint:MPCheckpointSitesExported];
|
||||
#endif
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesExported attributes:nil];
|
||||
|
||||
return export;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Pearl.h"
|
||||
|
||||
@interface MPConfig : PearlConfig
|
||||
|
||||
@property (nonatomic, retain) NSNumber *saveKey;
|
||||
@property (nonatomic, retain) NSNumber *rememberKey;
|
||||
@property (nonatomic, retain) NSNumber *rememberLogin;
|
||||
|
||||
@property (nonatomic, retain) NSNumber *iCloud;
|
||||
@property (nonatomic, retain) NSNumber *iCloudDecided;
|
||||
|
||||
@@ -6,33 +6,31 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPConfig.h"
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
@implementation MPConfig
|
||||
@dynamic saveKey, rememberKey, iCloud, iCloudDecided;
|
||||
@dynamic rememberLogin, iCloud, iCloudDecided;
|
||||
|
||||
- (id)init {
|
||||
|
||||
if(!(self = [super init]))
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
|
||||
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
|
||||
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(saveKey)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKey)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
|
||||
nil]];
|
||||
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
|
||||
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
|
||||
nil]];
|
||||
|
||||
self.delegate = [MPAppDelegate get];
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (MPConfig *)get {
|
||||
|
||||
|
||||
return (MPConfig *)[super get];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
//
|
||||
// MPElementEntity.h
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
@class MPUserEntity;
|
||||
|
||||
@interface MPElementEntity : NSManagedObject
|
||||
|
||||
@property (nonatomic, retain) NSString *name;
|
||||
@property (nonatomic, retain) NSData *keyID;
|
||||
@property (nonatomic, assign) int16_t type;
|
||||
@property (nonatomic, assign) int16_t uses;
|
||||
@property (nonatomic, assign) NSTimeInterval lastUsed;
|
||||
|
||||
@property (nonatomic, retain, readonly) id content;
|
||||
|
||||
- (int16_t)use;
|
||||
- (NSString *)exportContent;
|
||||
- (void)importContent:(NSString *)content;
|
||||
@property (nonatomic, retain) id content;
|
||||
@property (nonatomic, retain) NSDate * lastUsed;
|
||||
@property (nonatomic, retain) NSString * name;
|
||||
@property (nonatomic, retain) NSNumber * type_;
|
||||
@property (nonatomic, retain) NSNumber * uses_;
|
||||
@property (nonatomic, retain) MPUserEntity *user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,51 +1,22 @@
|
||||
//
|
||||
// MPElementEntity.m
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPUserEntity.h"
|
||||
|
||||
|
||||
@implementation MPElementEntity
|
||||
|
||||
@dynamic name;
|
||||
@dynamic keyID;
|
||||
@dynamic type;
|
||||
@dynamic uses;
|
||||
@dynamic content;
|
||||
@dynamic lastUsed;
|
||||
|
||||
- (int16_t)use {
|
||||
|
||||
self.lastUsed = [[NSDate date] timeIntervalSinceReferenceDate];
|
||||
return ++self.uses;
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
|
||||
}
|
||||
|
||||
- (NSString *)exportContent {
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)importContent:(NSString *)content {
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
|
||||
return PearlString(@"%@:%@", [self class], [self name]);
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
|
||||
return PearlString(@"{%@: name=%@, keyID=%@, type=%d, uses=%d, lastUsed=%@}",
|
||||
NSStringFromClass([self class]), self.name, self.keyID, self.type, self.uses, self.lastUsed);
|
||||
}
|
||||
@dynamic name;
|
||||
@dynamic type_;
|
||||
@dynamic uses_;
|
||||
@dynamic user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//
|
||||
// MPElementGeneratedEntity.h
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 16/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
@interface MPElementGeneratedEntity : MPElementEntity
|
||||
|
||||
@property (nonatomic, assign) int32_t counter;
|
||||
@property (nonatomic, retain) NSNumber * counter_;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,31 +1,16 @@
|
||||
//
|
||||
// MPElementGeneratedEntity.m
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 16/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
|
||||
@implementation MPElementGeneratedEntity
|
||||
|
||||
@dynamic counter;
|
||||
|
||||
- (id)content {
|
||||
|
||||
if (!(self.type & MPElementTypeClassGenerated)) {
|
||||
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![self.name length])
|
||||
return nil;
|
||||
|
||||
return MPCalculateContent((unsigned)self.type, self.name, [MPAppDelegate get].key, self.counter);
|
||||
}
|
||||
@dynamic counter_;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//
|
||||
// MPElementStoredEntity.h
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
@interface MPElementStoredEntity : MPElementEntity
|
||||
|
||||
@property (nonatomic, retain, readwrite) id content;
|
||||
@property (nonatomic, retain) id contentObject;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,76 +1,16 @@
|
||||
//
|
||||
// MPElementStoredEntity.m
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
@interface MPElementStoredEntity ()
|
||||
|
||||
@property (nonatomic, retain, readwrite) id contentObject;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPElementStoredEntity
|
||||
|
||||
@dynamic contentObject;
|
||||
|
||||
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"DevicePrivate", (__bridge id)kSecAttrService,
|
||||
name, (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
|
||||
NSData *encryptedContent;
|
||||
if (self.type & MPElementFeatureDevicePrivate)
|
||||
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
|
||||
else
|
||||
encryptedContent = self.contentObject;
|
||||
|
||||
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
|
||||
padding:YES];
|
||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
|
||||
padding:YES];
|
||||
|
||||
if (self.type & MPElementFeatureDevicePrivate) {
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
encryptedContent, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
self.contentObject = nil;
|
||||
} else
|
||||
self.contentObject = encryptedContent;
|
||||
}
|
||||
|
||||
- (NSString *)exportContent {
|
||||
|
||||
return [self.contentObject encodeBase64];
|
||||
}
|
||||
|
||||
- (void)importContent:(NSString *)content {
|
||||
|
||||
self.contentObject = [content decodeBase64];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
44
MasterPassword/MPEntities.h
Normal 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
@@ -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
|
||||
@@ -18,68 +18,71 @@ typedef enum {
|
||||
|
||||
typedef enum {
|
||||
/** Generate the password. */
|
||||
MPElementTypeClassGenerated = 1 << 4,
|
||||
MPElementTypeClassGenerated = 1 << 4,
|
||||
/** Store the password. */
|
||||
MPElementTypeClassStored = 1 << 5,
|
||||
MPElementTypeClassStored = 1 << 5,
|
||||
} MPElementTypeClass;
|
||||
|
||||
typedef enum {
|
||||
/** Export the key-protected content data. */
|
||||
MPElementFeatureExportContent = 1 << 10,
|
||||
MPElementFeatureExportContent = 1 << 10,
|
||||
/** Never export content. */
|
||||
MPElementFeatureDevicePrivate = 1 << 11,
|
||||
MPElementFeatureDevicePrivate = 1 << 11,
|
||||
} MPElementFeature;
|
||||
|
||||
typedef enum {
|
||||
MPElementTypeGeneratedLong = 0x0 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedMedium = 0x1 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedShort = 0x2 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedBasic = 0x3 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedPIN = 0x4 | MPElementTypeClassGenerated | 0x0,
|
||||
|
||||
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
||||
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
||||
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
|
||||
|
||||
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
||||
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
||||
} MPElementType;
|
||||
|
||||
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
|
||||
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
|
||||
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
|
||||
#define MPTestFlightCheckpointResetPasswordCounter @"MPTestFlightCheckpointResetPasswordCounter"
|
||||
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
|
||||
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
|
||||
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
|
||||
#define MPTestFlightCheckpointSelectType @"MPTestFlightCheckpointSelectType_%@"
|
||||
#define MPTestFlightCheckpointSelectElement @"MPTestFlightCheckpointSelectElement"
|
||||
#define MPTestFlightCheckpointDeleteElement @"MPTestFlightCheckpointDeleteElement"
|
||||
#define MPTestFlightCheckpointCancelSearch @"MPTestFlightCheckpointCancelSearch"
|
||||
#define MPTestFlightCheckpointExternalLink @"MPTestFlightCheckpointExternalLink"
|
||||
#define MPTestFlightCheckpointLaunched @"MPTestFlightCheckpointLaunched"
|
||||
#define MPTestFlightCheckpointActivated @"MPTestFlightCheckpointActivated"
|
||||
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
|
||||
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
|
||||
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
|
||||
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
|
||||
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
|
||||
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
|
||||
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
|
||||
#define MPTestFlightCheckpointMPEntered @"MPTestFlightCheckpointMPEntered"
|
||||
#define MPTestFlightCheckpointLocalStoreIncompatible @"MPTestFlightCheckpointLocalStoreIncompatible"
|
||||
#define MPTestFlightCheckpointCloudStoreIncompatible @"MPTestFlightCheckpointCloudStoreIncompatible"
|
||||
#define MPTestFlightCheckpointSetKey @"MPTestFlightCheckpointSetKey"
|
||||
#define MPTestFlightCheckpointCloudEnabled @"MPTestFlightCheckpointCloudEnabled"
|
||||
#define MPTestFlightCheckpointCloudDisabled @"MPTestFlightCheckpointCloudDisabled"
|
||||
#define MPTestFlightCheckpointSitesImported @"MPTestFlightCheckpointSitesImported"
|
||||
#define MPTestFlightCheckpointSitesExported @"MPTestFlightCheckpointSitesExported"
|
||||
#define MPCheckpointAction @"MPCheckpointAction"
|
||||
#define MPCheckpointHelpChapter @"MPCheckpointHelpChapter"
|
||||
#define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard"
|
||||
#define MPCheckpointResetPasswordCounter @"MPCheckpointResetPasswordCounter"
|
||||
#define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter"
|
||||
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
|
||||
#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
|
||||
#define MPCheckpointUseType @"MPCheckpointUseType"
|
||||
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
|
||||
#define MPCheckpointCancelSearch @"MPCheckpointCancelSearch"
|
||||
#define MPCheckpointExternalLink @"MPCheckpointExternalLink"
|
||||
#define MPCheckpointLaunched @"MPCheckpointLaunched"
|
||||
#define MPCheckpointActivated @"MPCheckpointActivated"
|
||||
#define MPCheckpointDeactivated @"MPCheckpointDeactivated"
|
||||
#define MPCheckpointTerminated @"MPCheckpointTerminated"
|
||||
#define MPCheckpointShowGuide @"MPCheckpointShowGuide"
|
||||
#define MPCheckpointForgetSavedKey @"MPCheckpointForgetSavedKey"
|
||||
#define MPCheckpointChangeMP @"MPCheckpointChangeMP"
|
||||
#define MPCheckpointLocalStoreIncompatible @"MPCheckpointLocalStoreIncompatible"
|
||||
#define MPCheckpointCloudStoreIncompatible @"MPCheckpointCloudStoreIncompatible"
|
||||
#define MPCheckpointSignInFailed @"MPCheckpointSignInFailed"
|
||||
#define MPCheckpointSignedIn @"MPCheckpointSignedIn"
|
||||
#define MPCheckpointConfig @"MPCheckpointConfig"
|
||||
#define MPCheckpointCloud @"MPCheckpointCloud"
|
||||
#define MPCheckpointCloudEnabled @"MPCheckpointCloudEnabled"
|
||||
#define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
|
||||
#define MPCheckpointSitesImported @"MPCheckpointSitesImported"
|
||||
#define MPCheckpointSitesExported @"MPCheckpointSitesExported"
|
||||
|
||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
||||
#define MPNotificationKeySet @"MPNotificationKeySet"
|
||||
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
|
||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
||||
#define MPNotificationSignedIn @"MPNotificationKeySet"
|
||||
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
|
||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
||||
#define MPNotificationElementUsed @"MPNotificationElementUsed"
|
||||
|
||||
NSData *keyForPassword(NSString *password);
|
||||
NSData *keyIDForPassword(NSString *password);
|
||||
NSData *keyIDForKey(NSData *key);
|
||||
NSData *keyForPassword(NSString *password, NSString *username);
|
||||
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username);
|
||||
NSData *keyIDForKey(NSData *key);
|
||||
NSString *NSStringFromMPElementType(MPElementType type);
|
||||
NSString *NSStringShortFromMPElementType(MPElementType type);
|
||||
NSString *ClassNameFromMPElementType(MPElementType type);
|
||||
Class ClassFromMPElementType(MPElementType type);
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter);
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);
|
||||
|
||||
@@ -6,158 +6,216 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTypes.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
|
||||
#define MP_salt nil
|
||||
#define MP_N 16384
|
||||
#define MP_N 131072
|
||||
#define MP_r 8
|
||||
#define MP_p 1
|
||||
#define MP_dkLen 64
|
||||
#define MP_hash PearlDigestSHA256
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
NSData *keyForPassword(NSString *password) {
|
||||
|
||||
NSData *keyForPassword(NSString *password, NSString *username) {
|
||||
|
||||
uint32_t nusernameLength = htonl(username.length);
|
||||
NSDate *start = [NSDate date];
|
||||
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
|
||||
|
||||
trc(@"Password: %@ derives to key ID: %@", password, [keyIDForKey(key) encodeHex]);
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nusernameLength
|
||||
length:sizeof(nusernameLength)],
|
||||
[username dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
|
||||
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2f)", username, password, [keyIDForKey(key) encodeHex], -[start timeIntervalSinceNow]);
|
||||
return key;
|
||||
}
|
||||
NSData *keyIDForPassword(NSString *password) {
|
||||
|
||||
return keyIDForKey(keyForPassword(password));
|
||||
|
||||
|
||||
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) {
|
||||
|
||||
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
|
||||
}
|
||||
|
||||
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username) {
|
||||
|
||||
return keyIDForKey(keyForPassword(password, username));
|
||||
}
|
||||
|
||||
NSData *keyIDForKey(NSData *key) {
|
||||
|
||||
|
||||
return [key hashWith:MP_hash];
|
||||
}
|
||||
|
||||
NSString *NSStringFromMPElementType(MPElementType type) {
|
||||
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
return @"Maximum Security Password";
|
||||
|
||||
case MPElementTypeGeneratedLong:
|
||||
return @"Long Password";
|
||||
|
||||
|
||||
case MPElementTypeGeneratedMedium:
|
||||
return @"Medium Password";
|
||||
|
||||
|
||||
case MPElementTypeGeneratedShort:
|
||||
return @"Short Password";
|
||||
|
||||
|
||||
case MPElementTypeGeneratedBasic:
|
||||
return @"Basic Password";
|
||||
|
||||
|
||||
case MPElementTypeGeneratedPIN:
|
||||
return @"PIN";
|
||||
|
||||
|
||||
case MPElementTypeStoredPersonal:
|
||||
return @"Personal Password";
|
||||
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return @"Device Private Password";
|
||||
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type];
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
}
|
||||
|
||||
NSString *NSStringShortFromMPElementType(MPElementType type) {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
return @"Maximum";
|
||||
|
||||
case MPElementTypeGeneratedLong:
|
||||
return @"Long";
|
||||
|
||||
case MPElementTypeGeneratedMedium:
|
||||
return @"Medium";
|
||||
|
||||
case MPElementTypeGeneratedShort:
|
||||
return @"Short";
|
||||
|
||||
case MPElementTypeGeneratedBasic:
|
||||
return @"Basic";
|
||||
|
||||
case MPElementTypeGeneratedPIN:
|
||||
return @"PIN";
|
||||
|
||||
case MPElementTypeStoredPersonal:
|
||||
return @"Personal";
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return @"Device";
|
||||
|
||||
default:
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
}
|
||||
|
||||
Class ClassFromMPElementType(MPElementType type) {
|
||||
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
case MPElementTypeGeneratedLong:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
|
||||
case MPElementTypeGeneratedMedium:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
|
||||
case MPElementTypeGeneratedShort:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
|
||||
case MPElementTypeGeneratedBasic:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
|
||||
case MPElementTypeGeneratedPIN:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
|
||||
case MPElementTypeStoredPersonal:
|
||||
return [MPElementStoredEntity class];
|
||||
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return [MPElementStoredEntity class];
|
||||
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type];
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
}
|
||||
|
||||
NSString *ClassNameFromMPElementType(MPElementType type) {
|
||||
|
||||
|
||||
return NSStringFromClass(ClassFromMPElementType(type));
|
||||
}
|
||||
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter) {
|
||||
|
||||
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
|
||||
|
||||
if (!(type & MPElementTypeClassGenerated)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
|
||||
return nil;
|
||||
}
|
||||
if (!name) {
|
||||
if (!name.length) {
|
||||
err(@"Missing name.");
|
||||
return nil;
|
||||
}
|
||||
if (!key) {
|
||||
err(@"Key not set.");
|
||||
if (!key.length) {
|
||||
err(@"Missing key.");
|
||||
return nil;
|
||||
}
|
||||
uint32_t salt = (unsigned)counter;
|
||||
if (!counter)
|
||||
// Counter unset, go into OTP mode.
|
||||
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
|
||||
salt = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
|
||||
|
||||
// Counter unset, go into OTP mode.
|
||||
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
|
||||
counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
|
||||
|
||||
if (MPTypes_ciphers == nil)
|
||||
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
|
||||
withExtension:@"plist"]];
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password: sha1(name . '\0' . key . '\0' . salt)
|
||||
uint32_t nsalt = htonl(salt);
|
||||
trc(@"seed from: sha1(%@, %@, %u)", name, key, nsalt);
|
||||
NSData *seed = [[NSData dataByConcatenatingWithDelimitor:'\0' datas:
|
||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
key,
|
||||
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)],
|
||||
nil] hashWith:PearlDigestSHA1];
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter);
|
||||
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
|
||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
|
||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
|
||||
nil]
|
||||
hmacWith:PearlHashSHA256 key:key];
|
||||
trc(@"seed is: %@", seed);
|
||||
const char *seedBytes = seed.bytes;
|
||||
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
assert([seed length]);
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
||||
valueForKey:NSStringFromMPElementType(type)];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
||||
valueForKey:NSStringFromMPElementType(type)];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
||||
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
|
||||
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
assert([seed length] >= [cipher length] + 1);
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
uint16_t keyByte = htons(seedBytes[c + 1]);
|
||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
|
||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
|
||||
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)];
|
||||
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length],
|
||||
1)];
|
||||
|
||||
trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
|
||||
[content appendString:character];
|
||||
}
|
||||
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
32
MasterPassword/MPUserEntity.h
Normal 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
|
||||
23
MasterPassword/MPUserEntity.m
Normal 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
|
||||
@@ -10,16 +10,16 @@
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPPasswordWindowController.h"
|
||||
|
||||
@interface MPAppDelegate : MPAppDelegate_Shared <NSApplicationDelegate>
|
||||
@interface MPAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
|
||||
|
||||
@property (strong) NSStatusItem *statusItem;
|
||||
@property (strong) MPPasswordWindowController *passwordWindow;
|
||||
@property (weak) IBOutlet NSMenuItem *lockItem;
|
||||
@property (weak) IBOutlet NSMenuItem *showItem;
|
||||
@property (strong) IBOutlet NSMenu *statusMenu;
|
||||
@property (weak) IBOutlet NSMenuItem *useICloudItem;
|
||||
@property (weak) IBOutlet NSMenuItem *rememberPasswordItem;
|
||||
@property (weak) IBOutlet NSMenuItem *savePasswordItem;
|
||||
@property (strong) NSStatusItem *statusItem;
|
||||
@property (strong) MPPasswordWindowController *passwordWindow;
|
||||
@property (weak) IBOutlet NSMenuItem *lockItem;
|
||||
@property (weak) IBOutlet NSMenuItem *showItem;
|
||||
@property (strong) IBOutlet NSMenu *statusMenu;
|
||||
@property (weak) IBOutlet NSMenuItem *useICloudItem;
|
||||
@property (weak) IBOutlet NSMenuItem *rememberPasswordItem;
|
||||
@property (weak) IBOutlet NSMenuItem *savePasswordItem;
|
||||
|
||||
+ (MPAppDelegate *)get;
|
||||
|
||||
|
||||
@@ -27,50 +27,52 @@
|
||||
@synthesize key;
|
||||
@synthesize keyID;
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wfour-char-constants"
|
||||
static EventHotKeyID MPShowHotKey = { .signature = 'show', .id = 1 };
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wfour-char-constants"
|
||||
static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1};
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
+ (void)initialize {
|
||||
|
||||
|
||||
[MPConfig get];
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelTrace;
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (MPAppDelegate *)get {
|
||||
|
||||
|
||||
return (MPAppDelegate *)[super get];
|
||||
}
|
||||
|
||||
static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData){
|
||||
|
||||
static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) {
|
||||
|
||||
// Extract the hotkey ID.
|
||||
EventHotKeyID hotKeyID;
|
||||
GetEventParameter(theEvent,kEventParamDirectObject,typeEventHotKeyID,
|
||||
NULL,sizeof(hotKeyID),NULL,&hotKeyID);
|
||||
|
||||
EventHotKeyID hotKeyID;
|
||||
GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID,
|
||||
NULL, sizeof(hotKeyID), NULL, &hotKeyID);
|
||||
|
||||
// Check which hotkey this was.
|
||||
if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) {
|
||||
[((__bridge MPAppDelegate *)userData) activate:nil];
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
return eventNotHandledErr;
|
||||
}
|
||||
|
||||
- (void)showMenu {
|
||||
|
||||
|
||||
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
|
||||
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
|
||||
self.showItem.enabled = ![self.passwordWindow.window isVisible];
|
||||
|
||||
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
|
||||
self.showItem.enabled = ![self.passwordWindow.window isVisible];
|
||||
|
||||
[self.statusItem popUpStatusItemMenu:self.statusMenu];
|
||||
}
|
||||
|
||||
- (IBAction)activate:(id)sender {
|
||||
|
||||
|
||||
if ([[NSApplication sharedApplication] isActive])
|
||||
[self applicationDidBecomeActive:nil];
|
||||
else
|
||||
@@ -78,13 +80,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
}
|
||||
|
||||
- (IBAction)togglePreference:(NSMenuItem *)sender {
|
||||
|
||||
|
||||
if (sender == useICloudItem)
|
||||
[self.storeManager useiCloudStore:sender.state == NSOffState alertUser:YES];
|
||||
if (sender == rememberPasswordItem)
|
||||
[MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]];
|
||||
if (sender == savePasswordItem)
|
||||
[MPConfig get].saveKey = [NSNumber numberWithBool:![[MPConfig get].saveKey boolValue]];
|
||||
[MPConfig get].saveKey = [NSNumber numberWithBool:![[MPConfig get].saveKey boolValue]];
|
||||
}
|
||||
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
|
||||
@@ -92,11 +94,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
if (configKey == @selector(rememberKey))
|
||||
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
|
||||
if (configKey == @selector(saveKey))
|
||||
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
|
||||
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
|
||||
|
||||
if ([keyPath isEqualToString:@"key"]) {
|
||||
if (self.key)
|
||||
[self.lockItem setEnabled:YES];
|
||||
@@ -108,49 +110,50 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
}
|
||||
|
||||
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
|
||||
|
||||
|
||||
return [[self managedObjectContext] undoManager];
|
||||
}
|
||||
|
||||
#pragma mark - NSApplicationDelegate
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
|
||||
|
||||
// Setup delegates and listeners.
|
||||
[MPConfig get].delegate = self;
|
||||
[self addObserver:self forKeyPath:@"key" options:0 context:nil];
|
||||
|
||||
|
||||
// Initially, use iCloud.
|
||||
if ([[MPConfig get].firstRun boolValue])
|
||||
[[self storeManager] useiCloudStore:YES alertUser:YES];
|
||||
|
||||
|
||||
// Status item.
|
||||
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
|
||||
self.statusItem.title = @"•••";
|
||||
self.statusItem.highlightMode = YES;
|
||||
self.statusItem.target = self;
|
||||
self.statusItem.action = @selector(showMenu);
|
||||
|
||||
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
|
||||
self.statusItem.title = @"•••";
|
||||
self.statusItem.highlightMode = YES;
|
||||
self.statusItem.target = self;
|
||||
self.statusItem.action = @selector(showMenu);
|
||||
|
||||
// Global hotkey.
|
||||
EventHotKeyRef hotKeyRef;
|
||||
EventTypeSpec hotKeyEvents[1] = { { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed } };
|
||||
OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents), hotKeyEvents,
|
||||
(__bridge void *)self, NULL);
|
||||
if(status != noErr)
|
||||
err(@"Error installing application event handler: %d", status);
|
||||
status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef);
|
||||
if(status != noErr)
|
||||
err(@"Error registering hotkey: %d", status);
|
||||
EventTypeSpec hotKeyEvents[1] = {{.eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed}};
|
||||
OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents),
|
||||
hotKeyEvents,
|
||||
(__bridge void *)self, NULL);
|
||||
if (status != noErr)
|
||||
err(@"Error installing application event handler: %d", status);
|
||||
status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef);
|
||||
if (status != noErr)
|
||||
err(@"Error registering hotkey: %d", status);
|
||||
}
|
||||
|
||||
- (void)applicationWillBecomeActive:(NSNotification *)notification {
|
||||
|
||||
|
||||
if (!self.passwordWindow)
|
||||
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
|
||||
|
||||
static BOOL firstTime = YES;
|
||||
if (firstTime)
|
||||
firstTime = NO;
|
||||
@@ -159,62 +162,61 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
|
||||
|
||||
if (![[MPConfig get].rememberKey boolValue])
|
||||
self.key = nil;
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
|
||||
{
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
// Save changes in the application's managed object context before the application terminates.
|
||||
|
||||
|
||||
if (![self managedObjectContext]) {
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
|
||||
if (![[self managedObjectContext] commitEditing]) {
|
||||
NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd));
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
|
||||
if (![[self managedObjectContext] hasChanges]) {
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
|
||||
NSError *error = nil;
|
||||
if (![[self managedObjectContext] save:&error]) {
|
||||
|
||||
|
||||
// Customize this code block to include application-specific recovery steps.
|
||||
BOOL result = [sender presentError:error];
|
||||
if (result) {
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message");
|
||||
NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info");
|
||||
NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
|
||||
|
||||
NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message");
|
||||
NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info");
|
||||
NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
|
||||
NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title");
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:question];
|
||||
[alert setInformativeText:info];
|
||||
[alert addButtonWithTitle:quitButton];
|
||||
[alert addButtonWithTitle:cancelButton];
|
||||
|
||||
|
||||
NSInteger answer = [alert runModal];
|
||||
|
||||
|
||||
if (answer == NSAlertAlternateReturn) {
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
#pragma mark - UbiquityStoreManagerDelegate
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
|
||||
|
||||
self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState;
|
||||
|
||||
self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState;
|
||||
self.useICloudItem.enabled = !iCloudEnabled;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface MPPasswordWindowController : NSWindowController <NSTextFieldDelegate> {
|
||||
|
||||
@interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate> {
|
||||
|
||||
NSString *_content;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
@synthesize tipField;
|
||||
|
||||
- (void)windowDidLoad {
|
||||
|
||||
|
||||
[self setContent:@""];
|
||||
[self.tipField setStringValue:@""];
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self unlock];
|
||||
@@ -45,32 +45,32 @@
|
||||
NSString *newSiteName = [self.siteField stringValue];
|
||||
BOOL shouldComplete = [self.oldSiteName length] < [newSiteName length];
|
||||
self.oldSiteName = newSiteName;
|
||||
|
||||
|
||||
if ([self trySite])
|
||||
shouldComplete = NO;
|
||||
|
||||
|
||||
if (shouldComplete)
|
||||
[[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil];
|
||||
}];
|
||||
|
||||
|
||||
[super windowDidLoad];
|
||||
}
|
||||
|
||||
- (void)unlock {
|
||||
|
||||
|
||||
if (![MPAppDelegate get].key)
|
||||
// Try and load the key from the keychain.
|
||||
// Try and load the key from the keychain.
|
||||
[[MPAppDelegate get] loadStoredKey];
|
||||
|
||||
|
||||
if (![MPAppDelegate get].key)
|
||||
// Ask the user to set the key through his master password.
|
||||
// Ask the user to set the key through his master password.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([MPAppDelegate get].key)
|
||||
return;
|
||||
|
||||
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
|
||||
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
|
||||
informativeTextWithFormat:@"Your master password is required to unlock the application."];
|
||||
|
||||
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
|
||||
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
|
||||
informativeTextWithFormat:@"Your master password is required to unlock the application."];
|
||||
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)];
|
||||
[alert setAccessoryView:passwordField];
|
||||
[alert layout];
|
||||
@@ -80,50 +80,52 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||
|
||||
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||
|
||||
switch (returnCode) {
|
||||
case NSAlertAlternateReturn:
|
||||
// "Change" button.
|
||||
if ([[NSAlert alertWithMessageText:@"Changing Master Password"
|
||||
defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil
|
||||
informativeTextWithFormat:
|
||||
@"This will allow you to log in with a different master password.\n\n"
|
||||
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
|
||||
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
|
||||
@"You can always change back to your current master password later.\n"
|
||||
@"Your current sites and passwords will then become available again."] runModal] == 1)
|
||||
informativeTextWithFormat:
|
||||
@"This will allow you to log in with a different master password.\n\n"
|
||||
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
|
||||
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
|
||||
@"You can always change back to your current master password later.\n"
|
||||
@"Your current sites and passwords will then become available again."] runModal]
|
||||
== 1)
|
||||
[[MPAppDelegate get] forgetKey];
|
||||
break;
|
||||
|
||||
|
||||
case NSAlertOtherReturn:
|
||||
// "Quit" button.
|
||||
[[NSApplication sharedApplication] terminate:self];
|
||||
return;
|
||||
|
||||
|
||||
case NSAlertDefaultReturn:
|
||||
// "Unlock" button.
|
||||
[[MPAppDelegate get] tryMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
|
||||
|
||||
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words
|
||||
forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
|
||||
|
||||
NSString *query = [[control stringValue] substringWithRange:charRange];
|
||||
if (![query length] || ![MPAppDelegate get].keyID)
|
||||
return nil;
|
||||
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND keyID == %@",
|
||||
query, query, [MPAppDelegate get].keyID];
|
||||
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
||||
query, query, [MPAppDelegate get].activeUser];
|
||||
|
||||
NSError *error = nil;
|
||||
self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];
|
||||
if (error)
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
|
||||
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
|
||||
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
|
||||
if (self.siteResults)
|
||||
for (MPElementEntity *element in self.siteResults)
|
||||
[mutableResults addObject:element.name];
|
||||
@@ -132,7 +134,7 @@
|
||||
}
|
||||
|
||||
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
|
||||
|
||||
|
||||
if (commandSelector == @selector(cancel:)) {
|
||||
[self.window close];
|
||||
return YES;
|
||||
@@ -149,68 +151,68 @@
|
||||
[self.tipField.animator setAlphaValue:0];
|
||||
[NSAnimationContext endGrouping];
|
||||
});
|
||||
|
||||
|
||||
[[self findElement] use];
|
||||
return YES;
|
||||
} else
|
||||
wrn(@"Couldn't copy password to pasteboard.");
|
||||
wrn(@"Couldn't copy password to pasteboard.");
|
||||
}
|
||||
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)controlTextDidEndEditing:(NSNotification *)obj {
|
||||
|
||||
|
||||
if (obj.object == self.siteField)
|
||||
[self trySite];
|
||||
}
|
||||
|
||||
- (NSString *)content {
|
||||
|
||||
|
||||
return _content;
|
||||
}
|
||||
|
||||
- (void)setContent:(NSString *)content {
|
||||
|
||||
|
||||
_content = content;
|
||||
|
||||
|
||||
NSShadow *shadow = [NSShadow new];
|
||||
shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f];
|
||||
shadow.shadowOffset = NSMakeSize(1.0f, -1.0f);
|
||||
shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f];
|
||||
shadow.shadowOffset = NSMakeSize(1.0f, -1.0f);
|
||||
shadow.shadowBlurRadius = 1.2f;
|
||||
|
||||
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
|
||||
paragraph.alignment = NSCenterTextAlignment;
|
||||
|
||||
|
||||
[self.contentField setAttributedStringValue:
|
||||
[[NSAttributedString alloc] initWithString:_content
|
||||
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
|
||||
shadow, NSShadowAttributeName,
|
||||
paragraph, NSParagraphStyleAttributeName,
|
||||
nil]]];
|
||||
[[NSAttributedString alloc] initWithString:_content
|
||||
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
|
||||
shadow, NSShadowAttributeName,
|
||||
paragraph, NSParagraphStyleAttributeName,
|
||||
nil]]];
|
||||
}
|
||||
|
||||
- (BOOL)trySite {
|
||||
|
||||
|
||||
MPElementEntity *result = [self findElement];
|
||||
if (!result) {
|
||||
[self setContent:@""];
|
||||
[self.tipField setStringValue:@""];
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSString *description = [result.content description];
|
||||
if (!description)
|
||||
description = @"";
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self setContent:description];
|
||||
[self.tipField setStringValue:@"Hit enter to copy the password."];
|
||||
self.tipField.alphaValue = 1;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// For when the app should be able to create new sites.
|
||||
/*
|
||||
else
|
||||
@@ -231,16 +233,16 @@
|
||||
});
|
||||
}];
|
||||
*/
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (MPElementEntity *)findElement {
|
||||
|
||||
|
||||
for (MPElementEntity *element in self.siteResults)
|
||||
if ([element.name isEqualToString:[self.siteField stringValue]])
|
||||
return element;
|
||||
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
//
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#endif
|
||||
|
||||
#import "Pearl-Prefix.pch"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
return NSApplicationMain(argc, (const char **)argv);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||
<attribute name="keyID" attributeType="Binary" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
||||
<attribute name="type" attributeType="Integer 16" defaultValueString="16" syncable="YES"/>
|
||||
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="counter" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
|
||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="120"/>
|
||||
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
|
||||
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -10,13 +10,14 @@
|
||||
#import <MessageUI/MessageUI.h>
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
@interface MPAppDelegate : MPAppDelegate_Shared <MFMailComposeViewControllerDelegate>
|
||||
@interface MPAppDelegate : MPAppDelegate_Shared<MFMailComposeViewControllerDelegate>
|
||||
|
||||
+ (MPAppDelegate *)get;
|
||||
|
||||
- (void)checkConfig;
|
||||
- (void)showGuide;
|
||||
- (void)loadKey:(BOOL)animated;
|
||||
|
||||
- (void)export;
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,21 +10,18 @@
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
#import "MPMainViewController.h"
|
||||
#import "IASKSettingsReader.h"
|
||||
#import "LocalyticsSession.h"
|
||||
#import "TestFlight.h"
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
|
||||
@interface MPAppDelegate ()
|
||||
|
||||
- (NSString *)testFlightInfo;
|
||||
- (NSDictionary *)testFlightInfo;
|
||||
- (NSString *)testFlightToken;
|
||||
|
||||
- (NSString *)crashlyticsInfo;
|
||||
- (NSDictionary *)crashlyticsInfo;
|
||||
- (NSString *)crashlyticsAPIKey;
|
||||
|
||||
- (NSString *)localyticsInfo;
|
||||
- (NSDictionary *)localyticsInfo;
|
||||
- (NSString *)localyticsKey;
|
||||
|
||||
@end
|
||||
@@ -33,9 +30,9 @@
|
||||
@implementation MPAppDelegate
|
||||
|
||||
+ (void)initialize {
|
||||
|
||||
|
||||
[MPiOSConfig get];
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelDebug;
|
||||
//[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
|
||||
@@ -43,223 +40,155 @@
|
||||
}
|
||||
|
||||
+ (MPAppDelegate *)get {
|
||||
|
||||
|
||||
return (MPAppDelegate *)[super get];
|
||||
}
|
||||
|
||||
- (void)showGuide {
|
||||
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
|
||||
}
|
||||
|
||||
- (void)loadKey:(BOOL)animated {
|
||||
|
||||
if (!self.key)
|
||||
// Try and load the key from the keychain.
|
||||
[self loadStoredKey];
|
||||
|
||||
if (!self.key)
|
||||
// Ask the user to set the key through his master password.
|
||||
if ([NSThread isMainThread])
|
||||
[self.navigationController presentViewController:
|
||||
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:animated completion:nil];
|
||||
else
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.navigationController presentViewController:
|
||||
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:animated completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)export {
|
||||
|
||||
[PearlAlert showNotice:
|
||||
@"This will export all your site names.\n\n"
|
||||
@"You can open the export with a text editor to get an overview of all your sites.\n\n"
|
||||
@"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes."
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
|
||||
@"Would you like to make all your passwords visible in the export?\n\n"
|
||||
@"A safe export will only include your stored passwords, in an encrypted manner, "
|
||||
@"making the result safe from falling in the wrong hands.\n\n"
|
||||
@"If all your passwords are shown and somebody else finds the export, "
|
||||
@"they could gain access to all your sites!"
|
||||
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 0)
|
||||
// Safe Export
|
||||
[self exportShowPasswords:NO];
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||
// Safe Export
|
||||
[self exportShowPasswords:YES];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
}
|
||||
|
||||
- (void)exportShowPasswords:(BOOL)showPasswords {
|
||||
|
||||
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
|
||||
NSString *message;
|
||||
if (showPasswords)
|
||||
message = @"Export of my Master Password sites with passwords visible.\n\nREMINDER: Make sure nobody else sees this file!\n";
|
||||
else
|
||||
message = @"Backup of my Master Password sites.\n";
|
||||
|
||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||
[exportDateFormatter setDateFormat:@"'Master Password sites ('yyyy'-'MM'-'DD').mpsites'"];
|
||||
|
||||
MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init];
|
||||
[composer setMailComposeDelegate:self];
|
||||
[composer setSubject:@"Master Password site export"];
|
||||
[composer setMessageBody:message isHTML:NO];
|
||||
[composer addAttachmentData:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
|
||||
fileName:[exportDateFormatter stringFromDate:[NSDate date]]];
|
||||
[self.window.rootViewController presentModalViewController:composer animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - PearlConfigDelegate
|
||||
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
|
||||
|
||||
[self checkConfig];
|
||||
}
|
||||
|
||||
- (void)checkConfig {
|
||||
|
||||
if ([[MPConfig get].saveKey boolValue]) {
|
||||
if (self.key)
|
||||
[self updateKey:self.key];
|
||||
} else
|
||||
[self loadStoredKey];
|
||||
|
||||
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
|
||||
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
|
||||
}
|
||||
|
||||
#pragma mark - UIApplicationDelegate
|
||||
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
#ifndef DEBUG
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
@try {
|
||||
NSString *testFlightToken = [self testFlightToken];
|
||||
if ([testFlightToken length]) {
|
||||
dbg(@"Initializing TestFlight");
|
||||
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
|
||||
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], @"logToConsole",
|
||||
[NSNumber numberWithBool:NO], @"logToSTDERR",
|
||||
nil]];
|
||||
[TestFlight takeOff:testFlightToken];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelInfo)
|
||||
TFLog(@"%@", message);
|
||||
|
||||
return YES;
|
||||
}];
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err(@"TestFlight: %@", exception);
|
||||
}
|
||||
@try {
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
dbg(@"Initializing Crashlytics");
|
||||
//[Crashlytics sharedInstance].debugMode = YES;
|
||||
[Crashlytics startWithAPIKey:crashlyticsAPIKey afterDelay:0];
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err(@"Crashlytics: %@", exception);
|
||||
}
|
||||
@try {
|
||||
NSString *localyticsKey = [self localyticsKey];
|
||||
if ([localyticsKey length]) {
|
||||
dbg(@"Initializing Localytics");
|
||||
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelError)
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[message levelDescription],
|
||||
@"level",
|
||||
message.message,
|
||||
@"message",
|
||||
nil]];
|
||||
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err(@"Localytics exception: %@", exception);
|
||||
}
|
||||
});
|
||||
|
||||
[[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
|
||||
@try {
|
||||
NSString *testFlightToken = [self testFlightToken];
|
||||
if ([testFlightToken length]) {
|
||||
inf(@"Initializing TestFlight");
|
||||
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
|
||||
#ifdef ADHOC
|
||||
[TestFlight setDeviceIdentifier:[(id)[UIDevice currentDevice] uniqueIdentifier]];
|
||||
#else
|
||||
[TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
#endif
|
||||
|
||||
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
|
||||
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], @"logToConsole",
|
||||
[NSNumber numberWithBool:NO], @"logToSTDERR",
|
||||
nil]];
|
||||
[TestFlight takeOff:testFlightToken];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
PearlLogLevel level = PearlLogLevelWarn;
|
||||
if ([[MPiOSConfig get].sendInfo boolValue])
|
||||
level = PearlLogLevelInfo;
|
||||
|
||||
if (message.level >= level)
|
||||
TFLog(@"%@", message);
|
||||
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"TestFlight: %@", exception);
|
||||
}
|
||||
@try {
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
inf(@"Initializing Crashlytics");
|
||||
#if defined (DEBUG) || defined (ADHOC)
|
||||
[Crashlytics sharedInstance].debugMode = YES;
|
||||
#endif
|
||||
[[Crashlytics sharedInstance] setObjectValue:@"Anonymous" forKey:@"username"];
|
||||
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
|
||||
[Crashlytics startWithAPIKey:crashlyticsAPIKey afterDelay:0];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
PearlLogLevel level = PearlLogLevelWarn;
|
||||
if ([[MPiOSConfig get].sendInfo boolValue])
|
||||
level = PearlLogLevelInfo;
|
||||
|
||||
if (message.level >= level)
|
||||
CLSLog(@"%@", message);
|
||||
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"Crashlytics: %@", exception);
|
||||
}
|
||||
@try {
|
||||
NSString *localyticsKey = [self localyticsKey];
|
||||
if ([localyticsKey length]) {
|
||||
inf(@"Initializing Localytics");
|
||||
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelWarn)
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[message levelDescription],
|
||||
@"level",
|
||||
message.message,
|
||||
@"message",
|
||||
nil]];
|
||||
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"Localytics exception: %@", exception);
|
||||
}
|
||||
|
||||
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
|
||||
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault];
|
||||
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UINavigationBar appearance] setTitleTextAttributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont,
|
||||
nil]];
|
||||
|
||||
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
|
||||
UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
|
||||
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Exo-Bold" size:20.0f], UITextAttributeFont,
|
||||
nil]];
|
||||
|
||||
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
|
||||
UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
|
||||
[[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UIBarButtonItem appearance] setTitleTextAttributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
|
||||
nil]
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
|
||||
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
|
||||
nil]
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
|
||||
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
|
||||
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
|
||||
|
||||
|
||||
/*
|
||||
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
|
||||
|
||||
|
||||
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
|
||||
|
||||
|
||||
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
||||
UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
|
||||
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
|
||||
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
|
||||
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
|
||||
|
||||
|
||||
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
|
||||
|
||||
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
|
||||
|
||||
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
|
||||
*/
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self checkConfig];
|
||||
}];
|
||||
|
||||
|
||||
#ifdef ADHOC
|
||||
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
|
||||
@"Thank you for taking the time to test Master Password.\n\n"
|
||||
@@ -268,58 +197,66 @@
|
||||
@"lhunath@lyndir.com\n"
|
||||
@"Or report detailed issues at:\n"
|
||||
@"https://youtrack.lyndir.com\n"
|
||||
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
|
||||
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
#endif
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO
|
||||
withAnimation:UIStatusBarAnimationSlide];
|
||||
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
[super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
|
||||
[TestFlight passCheckpoint:MPCheckpointLaunched];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLaunched];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
|
||||
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
|
||||
|
||||
__autoreleasing NSError *error;
|
||||
|
||||
__autoreleasing NSError *error;
|
||||
__autoreleasing NSURLResponse *response;
|
||||
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
||||
returningResponse:&response error:&error];
|
||||
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
||||
returningResponse:&response error:&error];
|
||||
if (error)
|
||||
err(@"While reading imported sites from %@: %@", url, error);
|
||||
err(@"While reading imported sites from %@: %@", url, error);
|
||||
if (!importedSitesData)
|
||||
return NO;
|
||||
|
||||
|
||||
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
||||
[PearlAlert showAlertWithTitle:@"Import Password" message:
|
||||
@"Enter the master password for this export:"
|
||||
viewStyle:UIAlertViewStyleSecureTextInput tappedButtonBlock:
|
||||
@"Enter the master password for this export:"
|
||||
viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:
|
||||
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text
|
||||
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
|
||||
__block BOOL confirmation = NO;
|
||||
|
||||
dispatch_group_t confirmationGroup = dispatch_group_create();
|
||||
dispatch_group_enter(confirmationGroup);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[PearlAlert showAlertWithTitle:@"Import Sites?"
|
||||
message:PearlLocalize(@"Import %d sites, overwriting %d existing sites?", importCount, deleteCount)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex != [alert cancelButtonIndex])
|
||||
confirmation = YES;
|
||||
|
||||
dispatch_group_leave(confirmationGroup);
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:@"Import", nil];
|
||||
});
|
||||
dispatch_group_wait(confirmationGroup, DISPATCH_TIME_FOREVER);
|
||||
|
||||
return confirmation;
|
||||
}];
|
||||
|
||||
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
|
||||
__block BOOL confirmation = NO;
|
||||
|
||||
dispatch_group_t confirmationGroup = dispatch_group_create();
|
||||
dispatch_group_enter(confirmationGroup);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[PearlAlert showAlertWithTitle:@"Import Sites?"
|
||||
message:PearlString(
|
||||
@"Import %d sites, overwriting %d existing sites?",
|
||||
importCount, deleteCount)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_
|
||||
!= [alert_ cancelButtonIndex])
|
||||
confirmation = YES;
|
||||
|
||||
dispatch_group_leave(confirmationGroup);
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:@"Import", nil];
|
||||
});
|
||||
dispatch_group_wait(
|
||||
confirmationGroup, DISPATCH_TIME_FOREVER);
|
||||
|
||||
return confirmation;
|
||||
}];
|
||||
|
||||
switch (result) {
|
||||
case MPImportResultSuccess:
|
||||
case MPImportResultCancelled:
|
||||
@@ -336,130 +273,306 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
|
||||
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
|
||||
|
||||
inf(@"Re-activated");
|
||||
[[MPAppDelegate get] checkConfig];
|
||||
|
||||
if ([[MPiOSConfig get].showQuickStart boolValue])
|
||||
[self showGuide];
|
||||
else {
|
||||
[self loadKey:NO];
|
||||
[self checkConfig];
|
||||
}
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointActivated];
|
||||
|
||||
[super applicationDidBecomeActive:application];
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
|
||||
|
||||
[super applicationDidEnterBackground:application];
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] resume];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
|
||||
|
||||
[super applicationWillEnterForeground:application];
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
|
||||
|
||||
[self saveContext];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
|
||||
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointTerminated];
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
|
||||
|
||||
[super applicationWillTerminate:application];
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
|
||||
|
||||
inf(@"Will deactivate");
|
||||
[self saveContext];
|
||||
|
||||
if (![[MPiOSConfig get].rememberKey boolValue]) {
|
||||
[self updateKey:nil];
|
||||
[self loadKey:NO];
|
||||
|
||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||
[self signOut];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointDeactivated];
|
||||
}
|
||||
|
||||
#pragma mark - Behavior
|
||||
|
||||
- (void)checkConfig {
|
||||
|
||||
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
|
||||
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
|
||||
if ([[MPiOSConfig get].sendInfo boolValue]) {
|
||||
if ([PearlLogger get].autoprintLevel > PearlLogLevelInfo)
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelInfo;
|
||||
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloud boolValue] forKey:@"iCloud"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloudDecided boolValue] forKey:@"iCloudDecided"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].sendInfo boolValue] forKey:@"sendInfo"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].helpHidden boolValue] forKey:@"helpHidden"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].showQuickStart boolValue] forKey:@"showQuickStart"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].firstRun boolValue] forKey:@"firstRun"];
|
||||
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].launchCount intValue] forKey:@"launchCount"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].askForReviews boolValue] forKey:@"askForReviews"];
|
||||
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].reviewAfterLaunches intValue] forKey:@"reviewAfterLaunches"];
|
||||
[[Crashlytics sharedInstance] setObjectValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
|
||||
|
||||
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].rememberLogin boolValue]? @"YES": @"NO" forKey:@"rememberLogin"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloud boolValue]? @"YES": @"NO" forKey:@"iCloud"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[MPConfig get].iCloudDecided boolValue]? @"YES": @"NO" forKey:@"iCloudDecided"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].sendInfo boolValue]? @"YES": @"NO" forKey:@"sendInfo"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].helpHidden boolValue]? @"YES": @"NO" forKey:@"helpHidden"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[MPiOSConfig get].showQuickStart boolValue]? @"YES": @"NO" forKey:@"showQuickStart"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].firstRun boolValue]? @"YES": @"NO" forKey:@"firstRun"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].launchCount description] forKey:@"launchCount"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].askForReviews boolValue]? @"YES": @"NO" forKey:@"askForReviews"];
|
||||
[TestFlight addCustomEnvironmentInformation:[[PearlConfig get].reviewAfterLaunches description] forKey:@"reviewAfterLaunches"];
|
||||
[TestFlight addCustomEnvironmentInformation:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointConfig];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointConfig attributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[[MPConfig get].rememberLogin boolValue]
|
||||
? @"YES": @"NO", @"rememberLogin",
|
||||
[[MPConfig get].iCloud boolValue]? @"YES"
|
||||
: @"NO", @"iCloud",
|
||||
[[MPConfig get].iCloudDecided boolValue]
|
||||
? @"YES": @"NO", @"iCloudDecided",
|
||||
[[MPiOSConfig get].sendInfo boolValue]
|
||||
? @"YES": @"NO", @"sendInfo",
|
||||
[[MPiOSConfig get].helpHidden boolValue]
|
||||
? @"YES": @"NO", @"helpHidden",
|
||||
[[MPiOSConfig get].showQuickStart boolValue]
|
||||
? @"YES": @"NO", @"showQuickStart",
|
||||
[[PearlConfig get].firstRun boolValue]
|
||||
? @"YES": @"NO", @"firstRun",
|
||||
[[PearlConfig get].launchCount description], @"launchCount",
|
||||
[[PearlConfig get].askForReviews boolValue]
|
||||
? @"YES": @"NO", @"askForReviews",
|
||||
[[PearlConfig get].reviewAfterLaunches description], @"reviewAfterLaunches",
|
||||
[PearlConfig get].reviewedVersion, @"reviewedVersion",
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showGuide {
|
||||
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointShowGuide];
|
||||
}
|
||||
|
||||
- (void)export {
|
||||
|
||||
[PearlAlert showNotice:
|
||||
@"This will export all your site names.\n\n"
|
||||
@"You can open the export with a text editor to get an overview of all your sites.\n\n"
|
||||
@"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes."
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
|
||||
@"Would you like to make all your passwords visible in the export?\n\n"
|
||||
@"A safe export will only include your stored passwords, in an encrypted manner, "
|
||||
@"making the result safe from falling in the wrong hands.\n\n"
|
||||
@"If all your passwords are shown and somebody else finds the export, "
|
||||
@"they could gain access to all your sites!"
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0)
|
||||
// Safe Export
|
||||
[self exportShowPasswords:NO];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
// Show Passwords
|
||||
[self exportShowPasswords:YES];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
}
|
||||
|
||||
- (void)exportShowPasswords:(BOOL)showPasswords {
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
|
||||
if (![MFMailComposeViewController canSendMail]) {
|
||||
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
|
||||
message:
|
||||
@"Your device is not yet set up for sending mail.\n"
|
||||
@"Close Master Password, go into Settings and add a Mail account."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:nil
|
||||
cancelTitle:[PearlStrings get].commonButtonOkay
|
||||
otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
|
||||
NSString *message;
|
||||
|
||||
if (showPasswords)
|
||||
message = PearlString(@"Export of Master Password sites with passwords included.\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
self.activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion);
|
||||
else
|
||||
message = PearlString(@"Backup of Master Password sites.\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
self.activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion);
|
||||
|
||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"];
|
||||
|
||||
MFMailComposeViewController *composer = [MFMailComposeViewController new];
|
||||
[composer setMailComposeDelegate:self];
|
||||
[composer setSubject:@"Master Password Export"];
|
||||
[composer setMessageBody:message isHTML:NO];
|
||||
[composer addAttachmentData:
|
||||
[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
|
||||
fileName:PearlString(@"%@ (%@).mpsites",
|
||||
self.activeUser.name,
|
||||
[exportDateFormatter stringFromDate:[NSDate date]])];
|
||||
[self.window.rootViewController presentModalViewController:composer animated:YES];
|
||||
}
|
||||
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Changing Master Password"
|
||||
message:
|
||||
@"If you continue, you'll be able to set a new master password.\n\n"
|
||||
@"Changing your master password will cause all your generated passwords to change!\n"
|
||||
@"Changing the master password back to the old one will cause your passwords to revert as well."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
inf(@"Unsetting master password for: %@.", user.userID);
|
||||
user.keyID = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
[self signOut];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointChangeMP];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP
|
||||
attributes:nil];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
#pragma mark - PearlConfigDelegate
|
||||
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
|
||||
|
||||
[self checkConfig];
|
||||
}
|
||||
|
||||
#pragma mark - MFMailComposeViewControllerDelegate
|
||||
|
||||
- (void)mailComposeController:(MFMailComposeViewController *)controller
|
||||
didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
|
||||
|
||||
|
||||
if (error)
|
||||
err(@"Error composing mail message: %@", error);
|
||||
|
||||
err(@"Error composing mail message: %@", error);
|
||||
|
||||
switch (result) {
|
||||
case MFMailComposeResultSaved:
|
||||
case MFMailComposeResultSent:
|
||||
break;
|
||||
|
||||
|
||||
case MFMailComposeResultFailed:
|
||||
[PearlAlert showError:@"A problem occurred while sending the message." tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
return;
|
||||
} otherTitles:@"Retry", nil];
|
||||
[PearlAlert showError:@"A problem occurred while sending the message."
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
return;
|
||||
} otherTitles:@"Retry", nil];
|
||||
return;
|
||||
case MFMailComposeResultCancelled:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
[controller dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - UbiquityStoreManagerDelegate
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
|
||||
|
||||
|
||||
[super ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
|
||||
|
||||
|
||||
if (![[MPConfig get].iCloudDecided boolValue]) {
|
||||
if (!iCloudEnabled) {
|
||||
[PearlAlert showAlertWithTitle:@"iCloud"
|
||||
message:
|
||||
@"iCloud is now disabled.\n\n"
|
||||
@"It is highly recommended you enable iCloud."
|
||||
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 0) {
|
||||
[PearlAlert showAlertWithTitle:@"About iCloud"
|
||||
message:
|
||||
@"iCloud is Apple's solution for saving your data in \"the cloud\" "
|
||||
@"and making sure your other iPhones, iPads and Macs are in sync.\n\n"
|
||||
@"For Master Password, that means your sites are available on all your "
|
||||
@"Apple devices, and you always have a backup of them in case "
|
||||
@"you loose one or need to restore.\n\n"
|
||||
@"Because of the way Master Password works, it doesn't need to send your "
|
||||
@"site's passwords to Apple. Only their names are saved to make it easier "
|
||||
@"for you to find the site you need. For some sites you may have set "
|
||||
@"a user-specified password: these are sent to iCloud after being encrypted "
|
||||
@"with your master password.\n\n"
|
||||
@"Apple can never see any of your passwords."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
@"iCloud is now disabled.\n\n"
|
||||
@"It is highly recommended you enable iCloud."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 0) {
|
||||
[PearlAlert showAlertWithTitle:@"About iCloud"
|
||||
message:
|
||||
@"iCloud is Apple's solution for saving your data in \"the cloud\" "
|
||||
@"and making sure your other iPhones, iPads and Macs are in sync.\n\n"
|
||||
@"For Master Password, that means your sites are available on all your "
|
||||
@"Apple devices, and you always have a backup of them in case "
|
||||
@"you loose one or need to restore.\n\n"
|
||||
@"Because of the way Master Password works, it doesn't need to send your "
|
||||
@"site's passwords to Apple. Only their names are saved to make it easier "
|
||||
@"for you to find the site you need. For some sites you may have set "
|
||||
@"a user-specified password: these are sent to iCloud after being encrypted "
|
||||
@"with your master password.\n\n"
|
||||
@"Apple can never see any of your passwords."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
[MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES];
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||
[manager useiCloudStore:YES alertUser:NO];
|
||||
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
|
||||
[MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES];
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||
[manager useiCloudStore:YES alertUser:NO];
|
||||
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -468,18 +581,18 @@
|
||||
|
||||
|
||||
- (NSDictionary *)testFlightInfo {
|
||||
|
||||
|
||||
static NSDictionary *testFlightInfo = nil;
|
||||
if (testFlightInfo == nil)
|
||||
testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
|
||||
|
||||
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
|
||||
|
||||
return testFlightInfo;
|
||||
}
|
||||
|
||||
- (NSString *)testFlightToken {
|
||||
|
||||
return NullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
|
||||
|
||||
return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
|
||||
}
|
||||
|
||||
|
||||
@@ -487,18 +600,18 @@
|
||||
|
||||
|
||||
- (NSDictionary *)crashlyticsInfo {
|
||||
|
||||
|
||||
static NSDictionary *crashlyticsInfo = nil;
|
||||
if (crashlyticsInfo == nil)
|
||||
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
|
||||
|
||||
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
|
||||
|
||||
return crashlyticsInfo;
|
||||
}
|
||||
|
||||
- (NSString *)crashlyticsAPIKey {
|
||||
|
||||
return NullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
|
||||
|
||||
return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
|
||||
}
|
||||
|
||||
|
||||
@@ -506,23 +619,21 @@
|
||||
|
||||
|
||||
- (NSDictionary *)localyticsInfo {
|
||||
|
||||
|
||||
static NSDictionary *localyticsInfo = nil;
|
||||
if (localyticsInfo == nil)
|
||||
localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
|
||||
|
||||
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
|
||||
|
||||
return localyticsInfo;
|
||||
}
|
||||
|
||||
- (NSString *)localyticsKey {
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
|
||||
#elif defined(LITE)
|
||||
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution.lite"]);
|
||||
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
|
||||
#else
|
||||
return NullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
|
||||
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface MPGuideViewController : UIViewController
|
||||
@interface MPGuideViewController : UIViewController <UIScrollViewDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
|
||||
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
|
||||
|
||||
- (IBAction)close;
|
||||
|
||||
|
||||
@@ -7,42 +7,49 @@
|
||||
//
|
||||
|
||||
#import "MPGuideViewController.h"
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
|
||||
@implementation MPGuideViewController
|
||||
@synthesize scrollView;
|
||||
@synthesize pageControl;
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
|
||||
return (interfaceOrientation == UIInterfaceOrientationPortrait);
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
[PearlUIUtils autoSizeContent:self.scrollView];
|
||||
[self.scrollView autoSizeContent];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
inf(@"Guide will appear.");
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
|
||||
inf(@"Guide will disappear.");
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
|
||||
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
|
||||
[super viewDidDisappear:animated];
|
||||
|
||||
[[MPAppDelegate get] loadKey:animated];
|
||||
}
|
||||
|
||||
|
||||
- (void)viewDidUnload {
|
||||
|
||||
[self setScrollView:nil];
|
||||
[self setPageControl:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
@@ -51,4 +58,12 @@
|
||||
[self.presentingViewController dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView_ {
|
||||
|
||||
NSInteger page = (NSInteger)(self.scrollView.contentOffset.x / self.scrollView.bounds.size.width);
|
||||
|
||||
self.pageControl.currentPage = page;
|
||||
self.pageControl.hidden = (page == self.pageControl.numberOfPages - 1);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,31 +6,34 @@
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <MessageUI/MessageUI.h>
|
||||
#import "MPTypeViewController.h"
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPSearchDelegate.h"
|
||||
#import "IASKAppSettingsViewController.h"
|
||||
|
||||
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate>
|
||||
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, MFMailComposeViewControllerDelegate>
|
||||
|
||||
@property (strong, nonatomic) MPElementEntity *activeElement;
|
||||
@property (strong, nonatomic) MPElementEntity *activeElement;
|
||||
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *contentField;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *typeButton;
|
||||
@property (weak, nonatomic) IBOutlet UIWebView *helpView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *siteName;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *alertContainer;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *alertTitle;
|
||||
@property (weak, nonatomic) IBOutlet UITextView *alertBody;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *contentField;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *typeButton;
|
||||
@property (weak, nonatomic) IBOutlet UIWebView *helpView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *siteName;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *alertContainer;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *alertTitle;
|
||||
@property (weak, nonatomic) IBOutlet UITextView *alertBody;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *typeTipContainer;
|
||||
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *resetPasswordCounterGesture;
|
||||
|
||||
@property (copy) void (^contentTipCleanup)(BOOL finished);
|
||||
|
||||
|
||||
@@ -10,17 +10,12 @@
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "IASKAppSettingsViewController.h"
|
||||
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
|
||||
@interface MPMainViewController (Private)
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated;
|
||||
- (void)updateWasAnimated:(BOOL)animated;
|
||||
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
|
||||
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task;
|
||||
@@ -45,84 +40,100 @@
|
||||
@synthesize alertBody = _alertBody;
|
||||
@synthesize contentTipBody = _contentTipBody;
|
||||
@synthesize contentTipEditIcon = _contentTipEditIcon;
|
||||
@synthesize searchTipContainer = _searchTip;
|
||||
@synthesize searchTipContainer = _searchTipContainer;
|
||||
@synthesize actionsTipContainer = _actionsTipContainer;
|
||||
@synthesize typeTipContainer = _typeTipContainer;
|
||||
@synthesize resetPasswordCounterGesture = _resetPasswordCounterGesture;
|
||||
@synthesize contentField = _contentField;
|
||||
@synthesize contentTipCleanup;
|
||||
|
||||
#pragma mark - View lifecycle
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad || interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
|
||||
|
||||
return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
|
||||
}
|
||||
|
||||
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
|
||||
|
||||
|
||||
[self setHelpHidden:![self isHelpVisible] animated:NO];
|
||||
}
|
||||
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([[segue identifier] isEqualToString:@"MP_Main_ChooseType"])
|
||||
|
||||
if ([[segue identifier] isEqualToString:@"MP_ChooseType"])
|
||||
((MPTypeViewController *)[segue destinationViewController]).delegate = self;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.searchTipContainer.hidden = NO;
|
||||
|
||||
if (![self.activeElement.keyID isEqualToData:[MPAppDelegate get].keyID])
|
||||
self.activeElement = nil;
|
||||
self.searchDisplayController.searchBar.text = nil;
|
||||
|
||||
if (!self.activeElement.name)
|
||||
[UIView animateWithDuration:animated? 0.2f: 0 animations:^{
|
||||
self.searchTipContainer.alpha = 1;
|
||||
}];
|
||||
|
||||
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
|
||||
[self updateAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
self.searchTipContainer.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.resetPasswordCounterGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(resetPasswordCounter:)];
|
||||
[self.passwordIncrementer addGestureRecognizer:self.resetPasswordCounterGesture];
|
||||
|
||||
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
|
||||
|
||||
|
||||
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize];
|
||||
|
||||
// Put the search tip on the window so it's above the nav bar.
|
||||
if (![self.searchTipContainer.superview isEqual:self.navigationController.navigationBar.superview]) {
|
||||
CGRect frameInWindow = [self.searchTipContainer.window convertRect:self.searchTipContainer.frame
|
||||
fromView:self.searchTipContainer.superview];
|
||||
[self.searchTipContainer removeFromSuperview];
|
||||
[self.navigationController.navigationBar.superview addSubview:self.searchTipContainer];
|
||||
self.searchTipContainer.frame = [self.searchTipContainer.window convertRect:frameInWindow
|
||||
toView:self.searchTipContainer.superview];
|
||||
}
|
||||
|
||||
self.alertBody.text = nil;
|
||||
|
||||
self.alertBody.text = nil;
|
||||
self.contentTipEditIcon.hidden = YES;
|
||||
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
inf(@"Main will appear.");
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
if (![MPAppDelegate get].activeUser)
|
||||
[self.navigationController presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:animated completion:nil];
|
||||
if (self.activeElement.user != [MPAppDelegate get].activeUser)
|
||||
self.activeElement = nil;
|
||||
self.searchDisplayController.searchBar.text = nil;
|
||||
|
||||
self.searchTipContainer.alpha = 0;
|
||||
self.actionsTipContainer.alpha = 0;
|
||||
self.typeTipContainer.alpha = 0;
|
||||
|
||||
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
|
||||
[self updateAnimated:animated];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
if ([[MPiOSConfig get].firstRun boolValue])
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
self.actionsTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.actionsTipContainer.alpha = 0;
|
||||
} completion:^(BOOL finished_) {
|
||||
if (![self.activeElement.name length])
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
self.searchTipContainer.alpha = 1;
|
||||
}];
|
||||
}];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
inf(@"Main will disappear.");
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidUnload {
|
||||
|
||||
|
||||
[self setContentField:nil];
|
||||
[self setTypeButton:nil];
|
||||
[self setSearchResultsController:nil];
|
||||
@@ -140,46 +151,42 @@
|
||||
[self setContentTipBody:nil];
|
||||
[self setContentTipEditIcon:nil];
|
||||
[self setSearchTipContainer:nil];
|
||||
[self setActionsTipContainer:nil];
|
||||
[self setTypeTipContainer:nil];
|
||||
[self setResetPasswordCounterGesture:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated {
|
||||
|
||||
[[MPAppDelegate get] saveContext];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (animated)
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[self updateWasAnimated:animated];
|
||||
}];
|
||||
else
|
||||
[self updateWasAnimated:animated];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateWasAnimated:(BOOL)animated {
|
||||
|
||||
if (animated) {
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[self updateAnimated:NO];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
[self setHelpChapter:self.activeElement? @"2": @"1"];
|
||||
self.siteName.text = self.activeElement.name;
|
||||
|
||||
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
|
||||
|
||||
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
|
||||
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
|
||||
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
|
||||
|
||||
[self.typeButton setTitle:NSStringFromMPElementType((unsigned)self.activeElement.type)
|
||||
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
|
||||
|
||||
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
|
||||
forState:UIControlStateNormal];
|
||||
self.typeButton.alpha = NSStringFromMPElementType((unsigned)self.activeElement.type).length? 1: 0;
|
||||
|
||||
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
|
||||
|
||||
self.contentField.enabled = NO;
|
||||
|
||||
|
||||
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
self.passwordCounter.text = [NSString stringWithFormat:@"%u", ((MPElementGeneratedEntity *) self.activeElement).counter];
|
||||
|
||||
self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter);
|
||||
|
||||
self.contentField.text = @"";
|
||||
if (self.activeElement.name && ![self.activeElement isDeleted])
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSString *description = [self.activeElement.content description];
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.contentField.text = description;
|
||||
});
|
||||
@@ -187,26 +194,26 @@
|
||||
}
|
||||
|
||||
- (BOOL)isHelpVisible {
|
||||
|
||||
|
||||
return self.helpContainer.frame.origin.y == 216;
|
||||
}
|
||||
|
||||
- (void)toggleHelpAnimated:(BOOL)animated {
|
||||
|
||||
|
||||
[self setHelpHidden:[self isHelpVisible] animated:animated];
|
||||
}
|
||||
|
||||
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated {
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
if (hidden) {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
|
||||
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES];
|
||||
} else {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
|
||||
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO];
|
||||
}
|
||||
}];
|
||||
@@ -214,47 +221,46 @@
|
||||
}
|
||||
|
||||
- (void)setHelpChapter:(NSString *)chapter {
|
||||
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]];
|
||||
|
||||
|
||||
[TestFlight passCheckpoint:PearlString(MPCheckpointHelpChapter @"_%@", chapter)];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.helpView loadRequest:
|
||||
[NSURLRequest requestWithURL:
|
||||
[NSURL URLWithString:[NSString stringWithFormat:@"#%@", chapter] relativeToURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]];
|
||||
NSURL *url = [NSURL URLWithString:[@"#" stringByAppendingString:chapter]
|
||||
relativeToURL:[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]];
|
||||
[self.helpView loadRequest:[NSURLRequest requestWithURL:url]];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
||||
|
||||
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
|
||||
ClassNameFromMPElementType((unsigned)self.activeElement.type)]];
|
||||
|
||||
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
|
||||
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
|
||||
if (error.length)
|
||||
err(@"helpView.setClass: %@", error);
|
||||
err(@"helpView.setClass: %@", error);
|
||||
}
|
||||
|
||||
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.contentTipCleanup)
|
||||
self.contentTipCleanup(NO);
|
||||
|
||||
|
||||
self.contentTipBody.text = message;
|
||||
self.contentTipCleanup = ^(BOOL finished) {
|
||||
icon.hidden = YES;
|
||||
self.contentTipCleanup = ^(BOOL finished) {
|
||||
icon.hidden = YES;
|
||||
self.contentTipCleanup = nil;
|
||||
};
|
||||
|
||||
|
||||
icon.hidden = NO;
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.contentTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.contentTipContainer.alpha = 0;
|
||||
} completion:self.contentTipCleanup];
|
||||
} completion:self.contentTipCleanup];
|
||||
});
|
||||
}
|
||||
}];
|
||||
@@ -262,7 +268,7 @@
|
||||
}
|
||||
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.alertTitle.text = title;
|
||||
NSRange scrollRange = NSMakeRange(self.alertBody.text.length, message.length);
|
||||
@@ -271,8 +277,8 @@
|
||||
else
|
||||
self.alertBody.text = message;
|
||||
[self.alertBody scrollRangeToVisible:scrollRange];
|
||||
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.alertContainer.alpha = 1;
|
||||
}];
|
||||
});
|
||||
@@ -281,268 +287,361 @@
|
||||
#pragma mark - Protocols
|
||||
|
||||
- (IBAction)copyContent {
|
||||
|
||||
|
||||
if (!self.activeElement)
|
||||
return;
|
||||
|
||||
[[UIPasteboard generalPasteboard] setValue:self.activeElement.content
|
||||
forPasteboardType:(id)kUTTypeUTF8PlainText];
|
||||
|
||||
|
||||
inf(@"Copying password for: %@", self.activeElement.name);
|
||||
[UIPasteboard generalPasteboard].string = [self.activeElement.content description];
|
||||
|
||||
[self showContentTip:@"Copied!" withIcon:nil];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointCopyToPasteboard];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
nil]];
|
||||
}
|
||||
|
||||
- (IBAction)incrementPasswordCounter {
|
||||
|
||||
|
||||
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
// Not of a type that supports a password counter.
|
||||
// Not of a type that supports a password counter.
|
||||
return;
|
||||
|
||||
|
||||
[self changeElementWithWarning:
|
||||
@"You are incrementing the site's password counter.\n\n"
|
||||
@"If you continue, a new password will be generated for this site. "
|
||||
@"You will then need to update your account's old password to this newly generated password.\n\n"
|
||||
@"You can reset the counter by holding down on this button."
|
||||
@"You are incrementing the site's password counter.\n\n"
|
||||
@"If you continue, a new password will be generated for this site. "
|
||||
@"You will then need to update your account's old password to this newly generated password.\n\n"
|
||||
@"You can reset the counter by holding down on this button."
|
||||
do:^{
|
||||
++((MPElementGeneratedEntity *) self.activeElement).counter;
|
||||
inf(@"Incrementing password counter for: %@", self.activeElement.name);
|
||||
++((MPElementGeneratedEntity *)self.activeElement).counter;
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
nil]];
|
||||
}];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter];
|
||||
}
|
||||
|
||||
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender {
|
||||
|
||||
|
||||
if (sender.state != UIGestureRecognizerStateBegan)
|
||||
// Only fire when the gesture was first detected.
|
||||
// Only fire when the gesture was first detected.
|
||||
return;
|
||||
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
// Not of a type that supports a password counter.
|
||||
// Not of a type that supports a password counter.
|
||||
return;
|
||||
if (((MPElementGeneratedEntity *)self.activeElement).counter == 1)
|
||||
// Counter has initial value, no point resetting.
|
||||
// Counter has initial value, no point resetting.
|
||||
return;
|
||||
|
||||
|
||||
[self changeElementWithWarning:
|
||||
@"You are resetting the site's password counter.\n\n"
|
||||
@"If you continue, the site's password will change back to its original value. "
|
||||
@"You will then need to update your account's password back to this original value."
|
||||
@"You are resetting the site's password counter.\n\n"
|
||||
@"If you continue, the site's password will change back to its original value. "
|
||||
@"You will then need to update your account's password back to this original value."
|
||||
do:^{
|
||||
((MPElementGeneratedEntity *) self.activeElement).counter = 1;
|
||||
inf(@"Resetting password counter for: %@", self.activeElement.name);
|
||||
((MPElementGeneratedEntity *)self.activeElement).counter = 1;
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
nil]];
|
||||
}];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter];
|
||||
}
|
||||
|
||||
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
|
||||
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[self changeElementWithoutWarningDo:task];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[self changeElementWithoutWarningDo:task];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
- (void)changeElementWithoutWarningDo:(void (^)(void))task; {
|
||||
|
||||
|
||||
// Update element, keeping track of the old password.
|
||||
NSString *oldPassword = [self.activeElement.content description];
|
||||
task();
|
||||
NSString *newPassword = [self.activeElement.content description];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
[self updateAnimated:YES];
|
||||
|
||||
|
||||
// Show new and old password.
|
||||
if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
|
||||
[self showAlertWithTitle:@"Password Changed!" message:PearlLocalize(@"The password for %@ has changed.\n\n"
|
||||
@"IMPORTANT:\n"
|
||||
@"Don't forget to update the site with your new password! "
|
||||
@"Your old password was:\n"
|
||||
@"%@", self.activeElement.name, oldPassword)];
|
||||
[self showAlertWithTitle:@"Password Changed!"
|
||||
message:PearlString(@"The password for %@ has changed.\n\n"
|
||||
@"IMPORTANT:\n"
|
||||
@"Don't forget to update the site with your new password! "
|
||||
@"Your old password was:\n"
|
||||
@"%@", self.activeElement.name, oldPassword)];
|
||||
}
|
||||
|
||||
|
||||
- (IBAction)editPassword {
|
||||
|
||||
|
||||
if (self.activeElement.type & MPElementTypeClassStored) {
|
||||
self.contentField.enabled = YES;
|
||||
[self.contentField becomeFirstResponder];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointEditPassword];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(
|
||||
self.activeElement.type), @"type",
|
||||
nil]];
|
||||
}
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword];
|
||||
}
|
||||
|
||||
- (IBAction)closeAlert {
|
||||
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.alertContainer.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
self.alertBody.text = nil;
|
||||
}];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointCloseAlert];
|
||||
}
|
||||
|
||||
- (IBAction)action:(id)sender {
|
||||
|
||||
|
||||
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
|
||||
switch (buttonIndex - [sheet firstOtherButtonIndex]) {
|
||||
case 0:
|
||||
case 0: {
|
||||
inf(@"Action: Toggle Help");
|
||||
[self toggleHelpAnimated:YES];
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
inf(@"Action: FAQ");
|
||||
[self setHelpChapter:@"faq"];
|
||||
[self setHelpHidden:NO animated:YES];
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
case 2: {
|
||||
inf(@"Action: Guide");
|
||||
[[MPAppDelegate get] showGuide];
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
IASKAppSettingsViewController *settingsVC = [IASKAppSettingsViewController new];
|
||||
settingsVC.delegate = self;
|
||||
[self.navigationController pushViewController:settingsVC animated:YES];
|
||||
}
|
||||
case 3: {
|
||||
inf(@"Action: Preferences");
|
||||
[self performSegueWithIdentifier:@"UserProfile" sender:self];
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
[[MPAppDelegate get] export];
|
||||
break;
|
||||
#ifdef ADHOC
|
||||
case 5:
|
||||
case 4: {
|
||||
inf(@"Action: Feedback via TestFlight");
|
||||
[TestFlight openFeedbackView];
|
||||
break;
|
||||
case 6:
|
||||
}
|
||||
case 5:
|
||||
#else
|
||||
case 4: {
|
||||
inf(@"Action: Feedback via Mail");
|
||||
if (![MFMailComposeViewController canSendMail])
|
||||
[PearlAlert showAlertWithTitle:@"Sending Feedback"
|
||||
message:
|
||||
@"We'd love to hear what you think!\n\n"
|
||||
@"Please send any comments or reports to:\n"
|
||||
@"masterpassword@lyndir.com"
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
|
||||
otherTitles:nil];
|
||||
|
||||
else {
|
||||
[PearlAlert showAlertWithTitle:@"Sending Feedback"
|
||||
message:
|
||||
@"We'd love to hear what you think!\n\n"
|
||||
@"If you're having trouble, it may help us if you can first reproduce the problem "
|
||||
@"and then include log files in your message."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
MFMailComposeViewController *composer = [MFMailComposeViewController new];
|
||||
[composer setMailComposeDelegate:self];
|
||||
[composer setToRecipients:[NSArray arrayWithObject:@"Master Password Development <masterpassword@lyndir.com>"]];
|
||||
[composer setSubject:PearlString(@"Feedback for Master Password [%@]", [[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
|
||||
[composer setMessageBody:
|
||||
PearlString(
|
||||
@"\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
[MPAppDelegate get].activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion)
|
||||
isHTML:NO];
|
||||
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex]) {
|
||||
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||
[composer addAttachmentData:[[[PearlLogger get] formatMessagesWithLevel:logLevel] dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain"
|
||||
fileName:PearlString(@"%@-%@.log",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
|
||||
[PearlKeyChain deviceIdentifier])];
|
||||
}
|
||||
|
||||
[self presentModalViewController:composer animated:YES];
|
||||
}
|
||||
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
#endif
|
||||
{
|
||||
[[MPAppDelegate get] signOut:self];
|
||||
[[MPAppDelegate get] loadKey:YES];
|
||||
inf(@"Action: Sign out");
|
||||
[[MPAppDelegate get] signOut];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointAction];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil
|
||||
otherTitles:
|
||||
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings", @"Export",
|
||||
#ifdef ADHOC
|
||||
@"Feedback",
|
||||
#endif
|
||||
@"Sign Out",
|
||||
nil];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointAction];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
|
||||
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
|
||||
}
|
||||
|
||||
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result
|
||||
error:(NSError *)error {
|
||||
|
||||
if (error)
|
||||
err(@"Feedback composer error: %@, result: %d", error, result);
|
||||
else
|
||||
inf(@"Feedback composer result: %d", result);
|
||||
|
||||
[controller dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (MPElementType)selectedType {
|
||||
|
||||
return (unsigned)self.activeElement.type;
|
||||
|
||||
return self.activeElement.type;
|
||||
}
|
||||
|
||||
- (void)didSelectType:(MPElementType)type {
|
||||
|
||||
|
||||
[self changeElementWithWarning:
|
||||
@"You are about to change the type of this password.\n\n"
|
||||
@"If you continue, the password for this site will change. "
|
||||
@"You will need to update your account's old password to the new one."
|
||||
@"You are about to change the type of this password.\n\n"
|
||||
@"If you continue, the password for this site will change. "
|
||||
@"You will need to update your account's old password to the new one."
|
||||
do:^{
|
||||
// Update password type.
|
||||
if (ClassFromMPElementType(type) != ClassFromMPElementType((unsigned)self.activeElement.type))
|
||||
// Type requires a different class of element. Recreate the element.
|
||||
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
|
||||
// Type requires a different class of element. Recreate the element.
|
||||
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(
|
||||
type)
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
newElement.name = self.activeElement.name;
|
||||
newElement.keyID = self.activeElement.keyID;
|
||||
newElement.uses = self.activeElement.uses;
|
||||
newElement.name = self.activeElement.name;
|
||||
newElement.user = self.activeElement.user;
|
||||
newElement.uses = self.activeElement.uses;
|
||||
newElement.lastUsed = self.activeElement.lastUsed;
|
||||
|
||||
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
|
||||
self.activeElement = newElement;
|
||||
}];
|
||||
|
||||
|
||||
self.activeElement.type = type;
|
||||
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
|
||||
|
||||
|
||||
if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length])
|
||||
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didSelectElement:(MPElementEntity *)element {
|
||||
|
||||
|
||||
inf(@"Selected: %@", element.name);
|
||||
|
||||
[self closeAlert];
|
||||
|
||||
|
||||
if (element) {
|
||||
self.activeElement = element;
|
||||
if ([self.activeElement use] == 1)
|
||||
[self showAlertWithTitle:@"New Site" message:
|
||||
PearlLocalize(@"You've just created a password for %@.\n\n"
|
||||
@"IMPORTANT:\n"
|
||||
@"Go to %@ and set or change the password for your account to the password above.\n"
|
||||
@"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.",
|
||||
self.activeElement.name, self.activeElement.name)];
|
||||
PearlString(@"You've just created a password for %@.\n\n"
|
||||
@"IMPORTANT:\n"
|
||||
@"Go to %@ and set or change the password for your account to the password above.\n"
|
||||
@"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.",
|
||||
self.activeElement.name, self.activeElement.name)];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
|
||||
|
||||
if ([[MPiOSConfig get].firstRun boolValue])
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.typeTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.typeTipContainer.alpha = 0;
|
||||
}];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
[self.searchDisplayController setActive:NO animated:YES];
|
||||
self.searchDisplayController.searchBar.text = self.activeElement.name;
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement];
|
||||
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", NSStringFromMPElementType(self.activeElement.type))];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(
|
||||
self.activeElement.type), @"type",
|
||||
nil]];
|
||||
}
|
||||
|
||||
|
||||
[self updateAnimated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
|
||||
if (textField == self.contentField)
|
||||
[self.contentField resignFirstResponder];
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
|
||||
|
||||
if (textField == self.contentField) {
|
||||
self.contentField.enabled = NO;
|
||||
if (![self.activeElement isKindOfClass:[MPElementStoredEntity class]])
|
||||
// Not of a type whose content can be edited.
|
||||
// Not of a type whose content can be edited.
|
||||
return;
|
||||
|
||||
if ([((MPElementStoredEntity *) self.activeElement).content isEqual:self.contentField.text])
|
||||
// Content hasn't changed.
|
||||
|
||||
if ([((MPElementStoredEntity *)self.activeElement).content isEqual:self.contentField.text])
|
||||
// Content hasn't changed.
|
||||
return;
|
||||
|
||||
|
||||
[self changeElementWithoutWarningDo:^{
|
||||
((MPElementStoredEntity *) self.activeElement).content = self.contentField.text;
|
||||
((MPElementStoredEntity *)self.activeElement).content = self.contentField.text;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
|
||||
navigationType:(UIWebViewNavigationType)navigationType {
|
||||
|
||||
|
||||
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink];
|
||||
|
||||
inf(@"External link: %@", [request URL]);
|
||||
[TestFlight passCheckpoint:MPCheckpointExternalLink];
|
||||
|
||||
[[UIApplication sharedApplication] openURL:[request URL]];
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
|
||||
|
||||
while ([self.navigationController.viewControllers containsObject:sender])
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
25
MasterPassword/iOS/MPPreferencesViewController.h
Normal 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
|
||||
169
MasterPassword/iOS/MPPreferencesViewController.m
Normal 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
|
||||
@@ -9,21 +9,21 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPElementEntity.h"
|
||||
|
||||
@protocol MPSearchResultsDelegate <NSObject>
|
||||
@protocol MPSearchResultsDelegate<NSObject>
|
||||
|
||||
- (void)didSelectElement:(MPElementEntity *)element;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
|
||||
@interface MPSearchDelegate : NSObject<UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
|
||||
|
||||
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
|
||||
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
|
||||
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
|
||||
@property (strong, nonatomic) NSString *query;
|
||||
@property (strong, nonatomic) UILabel *tipView;
|
||||
@property (strong, nonatomic) NSString *query;
|
||||
@property (strong, nonatomic) UILabel *tipView;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
|
||||
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
@property (strong, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
#import "MPSearchDelegate.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
@interface MPSearchDelegate (Private)
|
||||
|
||||
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)update;
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,48 +27,49 @@
|
||||
@synthesize searchTipContainer;
|
||||
|
||||
- (id)init {
|
||||
|
||||
|
||||
if (!([super init]))
|
||||
return nil;
|
||||
|
||||
self.dateFormatter = [NSDateFormatter new];
|
||||
|
||||
self.dateFormatter = [NSDateFormatter new];
|
||||
self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
|
||||
self.query = @"";
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
|
||||
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
|
||||
managedObjectContext:[MPAppDelegate managedObjectContext]
|
||||
sectionNameKeyPath:nil cacheName:nil];
|
||||
self.query = @"";
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
|
||||
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
|
||||
managedObjectContext:[MPAppDelegate managedObjectContext]
|
||||
sectionNameKeyPath:nil cacheName:nil];
|
||||
self.fetchedResultsController.delegate = self;
|
||||
|
||||
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
|
||||
self.tipView.textAlignment = UITextAlignmentCenter;
|
||||
self.tipView.backgroundColor = [UIColor clearColor];
|
||||
self.tipView.textColor = [UIColor lightTextColor];
|
||||
self.tipView.shadowColor = [UIColor blackColor];
|
||||
self.tipView.shadowOffset = CGSizeMake(0, -1);
|
||||
self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
|
||||
self.tipView.numberOfLines = 0;
|
||||
self.tipView.font = [UIFont systemFontOfSize:14];
|
||||
|
||||
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
|
||||
self.tipView.textAlignment = UITextAlignmentCenter;
|
||||
self.tipView.backgroundColor = [UIColor clearColor];
|
||||
self.tipView.textColor = [UIColor lightTextColor];
|
||||
self.tipView.shadowColor = [UIColor blackColor];
|
||||
self.tipView.shadowOffset = CGSizeMake(0, -1);
|
||||
self.tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
|
||||
| UIViewAutoresizingFlexibleBottomMargin;
|
||||
self.tipView.numberOfLines = 0;
|
||||
self.tipView.font = [UIFont systemFontOfSize:14];
|
||||
self.tipView.text =
|
||||
@"Tip:\n"
|
||||
@"Name your sites by their domain name:\n"
|
||||
@"apple.com, twitter.com\n\n"
|
||||
@"For email accounts, use the address:\n"
|
||||
@"john@apple.com, john@gmail.com";
|
||||
|
||||
@"Tip:\n"
|
||||
@"Name your sites by their domain name:\n"
|
||||
@"apple.com, twitter.com\n\n"
|
||||
@"For email accounts, use the address:\n"
|
||||
@"john@apple.com, john@gmail.com";
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
|
||||
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
|
||||
for (NSInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
|
||||
NSInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
|
||||
if (!rowCount)
|
||||
continue;
|
||||
|
||||
|
||||
if (rowCount == 1)
|
||||
[self tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
|
||||
break;
|
||||
@@ -77,69 +77,65 @@
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
|
||||
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointCancelSearch];
|
||||
|
||||
[self.delegate didSelectElement:nil];
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
|
||||
|
||||
if (searchBar.searchResultsButtonSelected && !searchText.length)
|
||||
searchBar.text = @" ";
|
||||
|
||||
self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
if (!self.query)
|
||||
self.query = @"";
|
||||
}
|
||||
|
||||
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
|
||||
controller.searchBar.prompt = @"Enter the site's name:";
|
||||
|
||||
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.searchTipContainer.alpha = 0;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
|
||||
controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
|
||||
self.query = @"";
|
||||
self.query = @"";
|
||||
}
|
||||
|
||||
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
controller.searchBar.prompt = nil;
|
||||
|
||||
dbg(@"Search ended with: %@", controller.searchBar.text);
|
||||
controller.searchBar.prompt = nil;
|
||||
controller.searchBar.searchResultsButtonSelected = NO;
|
||||
}
|
||||
|
||||
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
|
||||
|
||||
|
||||
tableView.backgroundColor = [UIColor blackColor];
|
||||
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
tableView.rowHeight = 48.0f;
|
||||
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
tableView.rowHeight = 48.0f;
|
||||
}
|
||||
|
||||
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
|
||||
|
||||
[self update];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
|
||||
if (!controller.active)
|
||||
return NO;
|
||||
|
||||
assert(self.query);
|
||||
|
||||
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND keyID == %@",
|
||||
self.query, self.query, NilToNull([MPAppDelegate get].keyID)];
|
||||
|
||||
|
||||
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
||||
self.query, self.query, NilToNSNull([MPAppDelegate get].activeUser)];
|
||||
|
||||
NSError *error;
|
||||
if (![self.fetchedResultsController performFetch:&error])
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
|
||||
NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews;
|
||||
NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1;
|
||||
UIView *overlay = [subviews count] > overlayIndex? [subviews objectAtIndex:overlayIndex]: nil;
|
||||
@@ -149,6 +145,8 @@
|
||||
[self.tipView removeFromSuperview];
|
||||
[overlay addSubview:self.tipView];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// See MP-14, also crashes easily on internal assertions etc..
|
||||
@@ -205,150 +203,158 @@
|
||||
//}
|
||||
|
||||
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
|
||||
|
||||
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
// [self.searchDisplayController.searchResultsTableView endUpdates];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
|
||||
|
||||
NSArray *sections = [self.fetchedResultsController sections];
|
||||
NSUInteger sectionCount = [sections count];
|
||||
|
||||
|
||||
if ([self.query length]) {
|
||||
__block BOOL hasExactQueryMatch = NO;
|
||||
[sections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
id<NSFetchedResultsSectionInfo> sectionInfo = obj;
|
||||
[[sectionInfo objects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([[obj name] isEqualToString:self.query]) {
|
||||
[[sectionInfo objects] enumerateObjectsUsingBlock:^(id obj_, NSUInteger idx_, BOOL *stop_) {
|
||||
if ([[obj_ name] isEqualToString:self.query]) {
|
||||
hasExactQueryMatch = YES;
|
||||
*stop = YES;
|
||||
*stop_ = YES;
|
||||
}
|
||||
}];
|
||||
if (hasExactQueryMatch)
|
||||
*stop = YES;
|
||||
*stop = YES;
|
||||
}];
|
||||
if (!hasExactQueryMatch)
|
||||
// Add a section for "new site".
|
||||
// Add a section for "new site".
|
||||
++sectionCount;
|
||||
}
|
||||
|
||||
return (signed)sectionCount;
|
||||
|
||||
return (NSInteger)sectionCount;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
|
||||
|
||||
NSArray *sections = [self.fetchedResultsController sections];
|
||||
if (section < (signed)[sections count])
|
||||
return (signed)[[sections objectAtIndex:(unsigned)section] numberOfObjects];
|
||||
|
||||
if (section < (NSInteger)[sections count])
|
||||
return (NSInteger)[[sections objectAtIndex:(unsigned)section] numberOfObjects];
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"];
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"];
|
||||
|
||||
|
||||
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]];
|
||||
backgroundImageView.frame = CGRectMake(-5, 0, 330, 34);
|
||||
backgroundImageView.frame = CGRectMake(-5, 0, 330, 34);
|
||||
backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f);
|
||||
backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f);
|
||||
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)];
|
||||
[backgroundView addSubview:backgroundImageView];
|
||||
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
cell.backgroundView = backgroundView;
|
||||
cell.textLabel.backgroundColor = [UIColor clearColor];
|
||||
cell.textLabel.textColor = [UIColor whiteColor];
|
||||
|
||||
cell.backgroundView = backgroundView;
|
||||
cell.textLabel.backgroundColor = [UIColor clearColor];
|
||||
cell.textLabel.textColor = [UIColor whiteColor];
|
||||
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
|
||||
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
|
||||
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
cell.clipsToBounds = YES;
|
||||
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
|
||||
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
cell.clipsToBounds = YES;
|
||||
}
|
||||
|
||||
|
||||
[self configureCell:cell inTableView:tableView atIndexPath:indexPath];
|
||||
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) {
|
||||
|
||||
if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) {
|
||||
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
|
||||
|
||||
cell.textLabel.text = element.name;
|
||||
|
||||
cell.textLabel.text = element.name;
|
||||
cell.detailTextLabel.text = [NSString stringWithFormat:@"Used %d times, last on %@",
|
||||
element.uses, [self.dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:element.lastUsed]]];
|
||||
element.uses, [self.dateFormatter stringFromDate:element.lastUsed]];
|
||||
} else {
|
||||
// "New" section
|
||||
cell.textLabel.text = self.query;
|
||||
cell.textLabel.text = self.query;
|
||||
cell.detailTextLabel.text = @"Create a new site.";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count])
|
||||
|
||||
if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count])
|
||||
[self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]];
|
||||
|
||||
|
||||
else {
|
||||
// "New" section.
|
||||
NSString *siteName = self.query;
|
||||
[PearlAlert showAlertWithTitle:@"New Site"
|
||||
message:PearlLocalize(@"Do you want to create a new site named:\n%@", siteName)
|
||||
message:PearlString(@"Do you want to create a new site named:\n%@", siteName)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
|
||||
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
|
||||
assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]);
|
||||
assert([MPAppDelegate get].keyID);
|
||||
|
||||
element.name = siteName;
|
||||
element.keyID = [MPAppDelegate get].keyID;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate didSelectElement:element];
|
||||
});
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementType type = [MPAppDelegate get].activeUser.defaultType;
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
|
||||
assert([MPAppDelegate get].activeUser);
|
||||
|
||||
element.name = siteName;
|
||||
element.user = [MPAppDelegate get].activeUser;
|
||||
element.type = type;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate didSelectElement:element];
|
||||
});
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
|
||||
|
||||
if (section < (signed)[[self.fetchedResultsController sections] count])
|
||||
|
||||
if (section < (NSInteger)[[self.fetchedResultsController sections] count])
|
||||
return [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name];
|
||||
|
||||
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
|
||||
|
||||
|
||||
return [self.fetchedResultsController sectionIndexTitles];
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
|
||||
|
||||
|
||||
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) {
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
|
||||
forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < (NSInteger)[[self.fetchedResultsController sections] count]) {
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete)
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
|
||||
|
||||
inf(@"Deleting element: %@", element.name);
|
||||
[self.fetchedResultsController.managedObjectContext deleteObject:element];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointDeleteElement];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointDeleteElement
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(element.type), @"type",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol MPTypeDelegate <NSObject>
|
||||
@protocol MPTypeDelegate<NSObject>
|
||||
|
||||
- (void)didSelectType:(MPElementType)type;
|
||||
|
||||
@@ -20,5 +20,6 @@
|
||||
@interface MPTypeViewController : UITableViewController
|
||||
|
||||
@property (nonatomic, weak) id<MPTypeDelegate> delegate;
|
||||
@property (weak, nonatomic) IBOutlet UIView *recommendedTipContainer;
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,95 +17,125 @@
|
||||
|
||||
@implementation MPTypeViewController
|
||||
@synthesize delegate;
|
||||
@synthesize recommendedTipContainer;
|
||||
|
||||
#pragma mark - View lifecycle
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
inf(@"Type selection will appear");
|
||||
self.recommendedTipContainer.alpha = 0;
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
if ([[MPiOSConfig get].firstRun boolValue])
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
self.recommendedTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.recommendedTipContainer.alpha = 0;
|
||||
}];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
|
||||
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
|
||||
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
inf(@"Type selection will disappear");
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
|
||||
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||
|
||||
|
||||
if ([delegate respondsToSelector:@selector(selectedType)])
|
||||
if ([delegate selectedType] == [self typeAtIndexPath:indexPath])
|
||||
[cell iterateSubviewsContinueAfter:^BOOL(UIView *subview) {
|
||||
if ([subview isKindOfClass:[UIImageView class]]) {
|
||||
UIImageView *imageView = ((UIImageView *)subview);
|
||||
if (!imageView.highlightedImage)
|
||||
imageView.highlightedImage = [imageView.image highlightedImage];
|
||||
imageView.highlighted = YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}];
|
||||
|
||||
cell.selected = ([delegate selectedType] == [self typeAtIndexPath:indexPath]);
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
|
||||
assert(self.navigationController.topViewController == self);
|
||||
|
||||
|
||||
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
|
||||
switch (indexPath.section) {
|
||||
case 0: {
|
||||
// Generated
|
||||
switch (indexPath.row) {
|
||||
case 0:
|
||||
return MPElementTypeGeneratedLong;
|
||||
return NSNotFound;
|
||||
case 1:
|
||||
return MPElementTypeGeneratedMedium;
|
||||
return MPElementTypeGeneratedMaximum;
|
||||
case 2:
|
||||
return MPElementTypeGeneratedShort;
|
||||
return MPElementTypeGeneratedLong;
|
||||
case 3:
|
||||
return MPElementTypeGeneratedBasic;
|
||||
return MPElementTypeGeneratedMedium;
|
||||
case 4:
|
||||
return MPElementTypeGeneratedShort;
|
||||
case 5:
|
||||
return MPElementTypeGeneratedBasic;
|
||||
case 6:
|
||||
return MPElementTypeGeneratedPIN;
|
||||
|
||||
case 7:
|
||||
return NSNotFound;
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"Unsupported row: %d, when selecting generated element type.", indexPath.row];
|
||||
Throw(@"Unsupported row: %d, when selecting generated element type.", indexPath.row);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 1: {
|
||||
// Stored
|
||||
switch (indexPath.row) {
|
||||
case 0:
|
||||
return MPElementTypeStoredPersonal;
|
||||
return NSNotFound;
|
||||
case 1:
|
||||
return MPElementTypeStoredPersonal;
|
||||
case 2:
|
||||
return MPElementTypeStoredDevicePrivate;
|
||||
|
||||
case 3:
|
||||
return NSNotFound;
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"Unsupported row: %d, when selecting stored element type.", indexPath.row];
|
||||
Throw(@"Unsupported row: %d, when selecting stored element type.", indexPath.row);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
|
||||
Throw(@"Unsupported section: %d, when selecting element type.", indexPath.section);
|
||||
}
|
||||
|
||||
@throw nil;
|
||||
}
|
||||
|
||||
- (void)viewDidUnload {
|
||||
|
||||
[self setRecommendedTipContainer:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -8,14 +8,22 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface MPUnlockViewController : UIViewController <UITextFieldDelegate>
|
||||
@interface MPUnlockViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *lock;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *spinner;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *field;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIView *changeMPView;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *spinner;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *passwordField;
|
||||
@property (weak, nonatomic) IBOutlet UIView *passwordView;
|
||||
@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *oldNameLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *deleteTip;
|
||||
@property (weak, nonatomic) IBOutlet UIView *passwordTipView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel;
|
||||
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *targetedUserActionGesture;
|
||||
|
||||
- (IBAction)changeMP;
|
||||
@property (nonatomic, strong) UIColor *avatarShadowColor;
|
||||
|
||||
- (IBAction)targetedUserAction:(UILongPressGestureRecognizer *)sender;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,245 +12,625 @@
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPElementEntity.h"
|
||||
|
||||
typedef enum {
|
||||
MPLockscreenIdle,
|
||||
MPLockscreenError,
|
||||
MPLockscreenSuccess,
|
||||
MPLockscreenProgress,
|
||||
} MPLockscreen;
|
||||
|
||||
@interface MPUnlockViewController ()
|
||||
|
||||
@property (strong, nonatomic) MPUserEntity *selectedUser;
|
||||
@property (strong, nonatomic) NSMutableDictionary *avatarToUser;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPUnlockViewController
|
||||
@synthesize lock;
|
||||
@synthesize selectedUser;
|
||||
@synthesize avatarToUser;
|
||||
@synthesize spinner;
|
||||
@synthesize field;
|
||||
@synthesize messageLabel;
|
||||
@synthesize changeMPView;
|
||||
@synthesize passwordField;
|
||||
@synthesize passwordView;
|
||||
@synthesize avatarsView;
|
||||
@synthesize nameLabel, oldNameLabel;
|
||||
@synthesize avatarTemplate;
|
||||
@synthesize deleteTip;
|
||||
@synthesize passwordTipView;
|
||||
@synthesize passwordTipLabel;
|
||||
@synthesize targetedUserActionGesture;
|
||||
@synthesize avatarShadowColor = _avatarShadowColor;
|
||||
|
||||
- (void)showMessage:(NSString *)message state:(MPLockscreen)state {
|
||||
|
||||
__block void(^showMessageAnimation)(void) = ^{
|
||||
self.lock.alpha = 0.0f;
|
||||
switch (state) {
|
||||
case MPLockscreenIdle:
|
||||
[self.lock setImage:[UIImage imageNamed:@"lock_idle"]];
|
||||
break;
|
||||
case MPLockscreenError:
|
||||
[self.lock setImage:[UIImage imageNamed:@"lock_red"]];
|
||||
break;
|
||||
case MPLockscreenSuccess:
|
||||
[self.lock setImage:[UIImage imageNamed:@"lock_green"]];
|
||||
break;
|
||||
case MPLockscreenProgress:
|
||||
[self.lock setImage:[UIImage imageNamed:@"lock_blue"]];
|
||||
break;
|
||||
}
|
||||
|
||||
self.lock.alpha = 0.0f;
|
||||
[UIView animateWithDuration:1.0f animations:^{
|
||||
self.lock.alpha = 1.0f;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
|
||||
self.lock.alpha = 0.5f;
|
||||
} completion:nil];
|
||||
}];
|
||||
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.messageLabel.alpha = 1.0f;
|
||||
self.messageLabel.text = message;
|
||||
}];
|
||||
};
|
||||
|
||||
if (self.messageLabel.alpha)
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.messageLabel.alpha = 0.0f;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
showMessageAnimation();
|
||||
}];
|
||||
else
|
||||
showMessageAnimation();
|
||||
|
||||
- (void)initAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
|
||||
UIScrollView *alertAvatarScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(12, 30, 260, 150)];
|
||||
alertAvatarScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
|
||||
[alertAvatarScrollView flashScrollIndicatorsContinuously];
|
||||
[alert addSubview:alertAvatarScrollView];
|
||||
|
||||
CGPoint selectedOffset = CGPointZero;
|
||||
for (int a = 0; a < MPAvatarCount; ++a) {
|
||||
UIButton *avatar = [self.avatarTemplate cloneAddedTo:alertAvatarScrollView];
|
||||
|
||||
avatar.tag = a;
|
||||
avatar.hidden = NO;
|
||||
avatar.center = CGPointMake(
|
||||
(20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
|
||||
20 + self.avatarTemplate.bounds.size.height / 2);
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", a)] forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
|
||||
|
||||
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
|
||||
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
avatar.layer.shadowOpacity = 1;
|
||||
avatar.layer.shadowRadius = 5;
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
|
||||
if (highlighted || selected)
|
||||
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
else
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
} options:0];
|
||||
[avatar onSelect:^(BOOL selected) {
|
||||
if (selected)
|
||||
user.avatar = (unsigned)avatar.tag;
|
||||
} options:0];
|
||||
avatar.selected = (a == user.avatar);
|
||||
if (avatar.selected)
|
||||
selectedOffset = CGPointMake(avatar.center.x - alertAvatarScrollView.bounds.size.width / 2, 0);
|
||||
}
|
||||
|
||||
[alertAvatarScrollView autoSizeContent];
|
||||
[alertAvatarScrollView setContentOffset:selectedOffset animated:YES];
|
||||
}
|
||||
|
||||
- (void)hideMessage {
|
||||
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.messageLabel.alpha = 0.0f;
|
||||
}];
|
||||
- (void)initConfirmationAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
|
||||
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(12, 70, 260, 110)];
|
||||
[alert addSubview:container];
|
||||
|
||||
UIButton *alertAvatar = [self.avatarTemplate cloneAddedTo:container];
|
||||
alertAvatar.center = CGPointMake(130, 55);
|
||||
alertAvatar.hidden = NO;
|
||||
alertAvatar.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
alertAvatar.layer.shadowOpacity = 1;
|
||||
alertAvatar.layer.shadowRadius = 5;
|
||||
alertAvatar.backgroundColor = [UIColor clearColor];
|
||||
[alertAvatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%d", user.avatar)] forState:UIControlStateNormal];
|
||||
|
||||
UILabel *alertNameLabel = [self.nameLabel cloneAddedTo:container];
|
||||
alertNameLabel.center = alertAvatar.center;
|
||||
alertNameLabel.text = user.name;
|
||||
alertNameLabel.bounds = CGRectSetHeight(alertNameLabel.bounds,
|
||||
[alertNameLabel.text sizeWithFont:self.nameLabel.font
|
||||
constrainedToSize:CGSizeMake(alertNameLabel.bounds.size.width - 10,
|
||||
100)
|
||||
lineBreakMode:self.nameLabel.lineBreakMode].height);
|
||||
alertNameLabel.layer.cornerRadius = 5;
|
||||
alertNameLabel.backgroundColor = [UIColor blackColor];
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
|
||||
return (interfaceOrientation == UIInterfaceOrientationPortrait);
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.messageLabel.text = nil;
|
||||
self.messageLabel.alpha = 0;
|
||||
self.changeMPView.alpha = 0;
|
||||
self.spinner.alpha = 0;
|
||||
self.field.text = nil;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten
|
||||
object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[self.field becomeFirstResponder];
|
||||
}];
|
||||
|
||||
|
||||
self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3];
|
||||
|
||||
self.avatarsView.decelerationRate = UIScrollViewDecelerationRateFast;
|
||||
self.avatarsView.clipsToBounds = NO;
|
||||
self.nameLabel.layer.cornerRadius = 5;
|
||||
self.avatarTemplate.hidden = YES;
|
||||
self.spinner.alpha = 0;
|
||||
self.passwordTipView.alpha = 0;
|
||||
|
||||
[self updateLayoutAnimated:NO allowScroll:YES completion:nil];
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
- (void)viewDidUnload {
|
||||
|
||||
|
||||
[self setSpinner:nil];
|
||||
[self setField:nil];
|
||||
|
||||
[self setMessageLabel:nil];
|
||||
[self setLock:nil];
|
||||
[self setChangeMPView:nil];
|
||||
[self setPasswordField:nil];
|
||||
[self setPasswordView:nil];
|
||||
[self setAvatarsView:nil];
|
||||
[self setNameLabel:nil];
|
||||
[self setAvatarTemplate:nil];
|
||||
[self setDeleteTip:nil];
|
||||
[self setPasswordTipView:nil];
|
||||
[self setPasswordTipLabel:nil];
|
||||
[self setTargetedUserActionGesture:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES
|
||||
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
|
||||
inf(@"Lock screen will appear");
|
||||
self.selectedUser = nil;
|
||||
[self updateUsers];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO
|
||||
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[self.field becomeFirstResponder];
|
||||
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
if ([textField.text length]) {
|
||||
[textField resignFirstResponder];
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
inf(@"Lock screen will disappear");
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
@try {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
|
||||
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
rotate.fromValue = [NSNumber numberWithFloat:0];
|
||||
rotate.toValue = [NSNumber numberWithFloat:2 * M_PI];
|
||||
rotate.repeatCount = MAXFLOAT;
|
||||
rotate.duration = 3.0;
|
||||
|
||||
[self.spinner.layer removeAllAnimations];
|
||||
[self.spinner.layer addAnimation:rotate forKey:@"transform"];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.spinner.alpha = 1.0f;
|
||||
}];
|
||||
|
||||
[self showMessage:@"Checking password..." state:MPLockscreenProgress];
|
||||
});
|
||||
|
||||
if ([[MPAppDelegate get] tryMasterPassword:textField.text])
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self showMessage:@"Success!" state:MPLockscreenSuccess];
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", [MPAppDelegate get].keyID];
|
||||
fetchRequest.fetchLimit = 1;
|
||||
BOOL keyIDHasElements = [[[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil] count] > 0;
|
||||
if (keyIDHasElements)
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{
|
||||
[self dismissModalViewControllerAnimated:YES];
|
||||
});
|
||||
else {
|
||||
[PearlAlert showAlertWithTitle:@"New Master Password"
|
||||
message:
|
||||
@"Please confirm the spelling of this new master password."
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex]) {
|
||||
[[MPAppDelegate get] updateKey:nil];
|
||||
return;
|
||||
}
|
||||
if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) {
|
||||
[PearlAlert showAlertWithTitle:@"Incorrect Master Password"
|
||||
message:
|
||||
@"The password you entered doesn't match with the master password you tried to use. "
|
||||
@"You've probably mistyped one of them.\n\n"
|
||||
@"Give it another try."
|
||||
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
|
||||
cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
[self dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
});
|
||||
- (void)updateUsers {
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
|
||||
NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
// Clean up avatars.
|
||||
for (UIView *subview in [self.avatarsView subviews])
|
||||
if ([[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
|
||||
// This subview is a former avatar.
|
||||
[subview removeFromSuperview];
|
||||
[self.avatarToUser removeAllObjects];
|
||||
|
||||
// Create avatars.
|
||||
for (MPUserEntity *user in users)
|
||||
[self setupAvatar:[self.avatarTemplate clone] forUser:user];
|
||||
[self setupAvatar:[self.avatarTemplate clone] forUser:nil];
|
||||
|
||||
// Scroll view's content changed, update its content size.
|
||||
[self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
|
||||
|
||||
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
|
||||
}
|
||||
|
||||
- (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user {
|
||||
|
||||
avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y);
|
||||
avatar.hidden = NO;
|
||||
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
|
||||
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
avatar.layer.shadowOpacity = 1;
|
||||
avatar.layer.shadowRadius = 20;
|
||||
avatar.layer.masksToBounds = NO;
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
avatar.tag = user.avatar;
|
||||
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)]
|
||||
forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:YES];
|
||||
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
|
||||
if (highlighted || selected)
|
||||
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
else
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
} options:0];
|
||||
[avatar onSelect:^(BOOL selected) {
|
||||
if (selected) {
|
||||
if ((self.selectedUser = user))
|
||||
[self didToggleUserSelection];
|
||||
else
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self showMessage:@"Not valid." state:MPLockscreenError];
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.changeMPView.alpha = 1.0f;
|
||||
}];
|
||||
});
|
||||
[self didSelectNewUserAvatar:avatar];
|
||||
} else {
|
||||
self.selectedUser = nil;
|
||||
[self didToggleUserSelection];
|
||||
}
|
||||
@finally {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.spinner.alpha = 0.0f;
|
||||
} completion:^(BOOL finished) {
|
||||
[self.spinner.layer removeAllAnimations];
|
||||
}];
|
||||
});
|
||||
} options:0];
|
||||
|
||||
[self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]];
|
||||
|
||||
if (self.selectedUser && user == self.selectedUser)
|
||||
avatar.selected = YES;
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
||||
- (void)didToggleUserSelection {
|
||||
|
||||
if (!self.selectedUser)
|
||||
[self.passwordField resignFirstResponder];
|
||||
else
|
||||
if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
if (self.selectedUser)
|
||||
[self.passwordField becomeFirstResponder];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
|
||||
|
||||
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
|
||||
[self showNewUserNameAlertFor:newUser completion:^(BOOL finished){
|
||||
newUserAvatar.selected = NO;
|
||||
if (!finished)
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:newUser];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showNewUserNameAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Enter Your Name"
|
||||
message:nil viewStyle:UIAlertViewStylePlainTextInput
|
||||
initAlert:^(UIAlertView *alert, UITextField *firstField) {
|
||||
firstField.autocapitalizationType = UITextAutocapitalizationTypeWords;
|
||||
firstField.keyboardType = UIKeyboardTypeAlphabet;
|
||||
firstField.text = newUser.name;
|
||||
firstField.placeholder = @"eg. Robert Lee Mitchell";
|
||||
}
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex]) {
|
||||
completion(NO);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save
|
||||
newUser.name = [alert textFieldAtIndex:0].text;
|
||||
[self showNewUserAvatarAlertFor:newUser completion:completion];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
|
||||
}
|
||||
|
||||
- (void)showNewUserAvatarAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Choose Your Avatar"
|
||||
message:@"\n\n\n\n\n\n" viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:^(UIAlertView *_alert, UITextField *_firstField) {
|
||||
[self initAvatarAlert:_alert forUser:newUser];
|
||||
}
|
||||
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
|
||||
|
||||
// Okay
|
||||
[self showNewUserConfirmationAlertFor:newUser completion:completion];
|
||||
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
}
|
||||
|
||||
- (void)showNewUserConfirmationAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Is this correct?"
|
||||
message:
|
||||
@"Please double-check your name.\n"
|
||||
@"\n\n\n\n\n\n"
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:^void(UIAlertView *__alert, UITextField *__firstField) {
|
||||
[self initConfirmationAlert:__alert forUser:newUser];
|
||||
}
|
||||
tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) {
|
||||
if (__buttonIndex == [__alert cancelButtonIndex]) {
|
||||
[self showNewUserNameAlertFor:newUser completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm
|
||||
completion(YES);
|
||||
self.selectedUser = newUser;
|
||||
|
||||
[self updateUsers];
|
||||
}
|
||||
cancelTitle:@"Change" otherTitles:@"Confirm", nil];
|
||||
}
|
||||
|
||||
- (void)updateLayoutAnimated:(BOOL)animated allowScroll:(BOOL)allowScroll completion:(void (^)(BOOL finished))completion {
|
||||
|
||||
if (animated) {
|
||||
self.oldNameLabel.text = self.nameLabel.text;
|
||||
self.oldNameLabel.alpha = 1;
|
||||
self.nameLabel.alpha = 0;
|
||||
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
[self updateLayoutAnimated:NO allowScroll:allowScroll completion:nil];
|
||||
|
||||
self.oldNameLabel.alpha = 0;
|
||||
self.nameLabel.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (completion)
|
||||
completion(finished);
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.selectedUser && !self.passwordView.alpha) {
|
||||
self.passwordView.alpha = 1;
|
||||
self.avatarsView.center = CGPointMake(160, 170);
|
||||
self.avatarsView.scrollEnabled = NO;
|
||||
self.nameLabel.center = CGPointMake(160, 84);
|
||||
self.nameLabel.backgroundColor = [UIColor blackColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor whiteColor];
|
||||
self.deleteTip.alpha = 0;
|
||||
} else
|
||||
if (!self.selectedUser && self.passwordView.alpha == 1) {
|
||||
self.passwordField.text = nil;
|
||||
self.passwordView.alpha = 0;
|
||||
self.avatarsView.center = CGPointMake(160, 310);
|
||||
self.avatarsView.scrollEnabled = YES;
|
||||
self.nameLabel.center = CGPointMake(160, 296);
|
||||
self.nameLabel.backgroundColor = [UIColor clearColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor lightGrayColor];
|
||||
self.deleteTip.alpha = 0.5;
|
||||
}
|
||||
|
||||
MPUserEntity *targetedUser = self.selectedUser;
|
||||
UIButton *selectedAvatar = [self avatarForUser:self.selectedUser];
|
||||
UIButton *targetedAvatar = selectedAvatar;
|
||||
if (!targetedAvatar) {
|
||||
targetedAvatar = [self findTargetedAvatar];
|
||||
targetedUser = [self userForAvatar:targetedAvatar];
|
||||
}
|
||||
|
||||
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
if (![[self.avatarToUser allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
|
||||
// This subview is not one of the user avatars.
|
||||
return;
|
||||
UIButton *avatar = (UIButton *)subview;
|
||||
|
||||
BOOL isTargeted = avatar == targetedAvatar;
|
||||
|
||||
avatar.userInteractionEnabled = isTargeted;
|
||||
avatar.alpha = isTargeted? 1: self.selectedUser? 0.1: 0.4;
|
||||
|
||||
[self updateAvatarShadowColor:avatar isTargeted:isTargeted];
|
||||
} recurse:NO];
|
||||
|
||||
if (allowScroll) {
|
||||
CGPoint targetContentOffset = CGPointMake(MAX(0, targetedAvatar.center.x - self.avatarsView.bounds.size.width / 2),
|
||||
self.avatarsView.contentOffset.y);
|
||||
if (!CGPointEqualToPoint(self.avatarsView.contentOffset, targetContentOffset))
|
||||
[self.avatarsView setContentOffset:targetContentOffset animated:animated];
|
||||
}
|
||||
|
||||
self.nameLabel.text = targetedUser? targetedUser.name: @"New User";
|
||||
self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds,
|
||||
[self.nameLabel.text sizeWithFont:self.nameLabel.font
|
||||
constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100)
|
||||
lineBreakMode:self.nameLabel.lineBreakMode].height);
|
||||
self.oldNameLabel.bounds = self.nameLabel.bounds;
|
||||
if (completion)
|
||||
completion(YES);
|
||||
}
|
||||
|
||||
- (void)setPasswordTip:(NSString *)string {
|
||||
|
||||
if (string.length)
|
||||
self.passwordTipLabel.text = string;
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.passwordTipView.alpha = string.length? 1: 0;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)tryMasterPassword {
|
||||
|
||||
[self setSpinnerActive:YES];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (unlocked)
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
else {
|
||||
if (self.passwordField.text.length)
|
||||
[self setPasswordTip:@"Incorrect password."];
|
||||
|
||||
[self setSpinnerActive:NO];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (IBAction)changeMP {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Changing Master Password"
|
||||
message:
|
||||
@"This will allow you to log in with a different master password.\n\n"
|
||||
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
|
||||
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
|
||||
@"You can always change back to your current master password later.\n"
|
||||
@"Your current sites and passwords will then become available again."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[[MPAppDelegate get] forgetKey];
|
||||
[[MPAppDelegate get] loadKey:YES];
|
||||
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
- (UIButton *)findTargetedAvatar {
|
||||
|
||||
CGFloat xOfMiddle = self.avatarsView.contentOffset.x + self.avatarsView.bounds.size.width / 2;
|
||||
return (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, self.avatarsView.contentOffset.y)
|
||||
ofArray:self.avatarsView.subviews];
|
||||
}
|
||||
|
||||
- (UIButton *)avatarForUser:(MPUserEntity *)user {
|
||||
|
||||
__block UIButton *avatar = nil;
|
||||
if (user)
|
||||
[self.avatarToUser enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
if (obj == user)
|
||||
avatar = [key nonretainedObjectValue];
|
||||
}];
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
||||
- (MPUserEntity *)userForAvatar:(UIButton *)avatar {
|
||||
|
||||
return NSNullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]);
|
||||
}
|
||||
|
||||
- (void)setSpinnerActive:(BOOL)active {
|
||||
|
||||
PearlMainThread(^{
|
||||
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
|
||||
rotate.toValue = [NSNumber numberWithDouble:2 * M_PI];
|
||||
rotate.duration = 5.0;
|
||||
|
||||
if (active) {
|
||||
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
rotate.fromValue = [NSNumber numberWithFloat:0];
|
||||
rotate.repeatCount = MAXFLOAT;
|
||||
} else {
|
||||
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
|
||||
rotate.repeatCount = 1;
|
||||
}
|
||||
|
||||
[self.spinner.layer removeAnimationForKey:@"rotation"];
|
||||
[self.spinner.layer addAnimation:rotate forKey:@"rotation"];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.spinner.alpha = active? 1: 0;
|
||||
|
||||
if (active)
|
||||
[self avatarForUser:self.selectedUser].backgroundColor = [UIColor clearColor];
|
||||
else
|
||||
[self avatarForUser:self.selectedUser].backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted {
|
||||
|
||||
if (targeted) {
|
||||
if (![avatar.layer animationForKey:@"targetedShadow"]) {
|
||||
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
|
||||
toShadowColorAnimation.toValue = (__bridge id)(avatar.selected? self.avatarTemplate.backgroundColor
|
||||
: [UIColor whiteColor]).CGColor;
|
||||
toShadowColorAnimation.beginTime = 0.0f;
|
||||
toShadowColorAnimation.duration = 0.5f;
|
||||
toShadowColorAnimation.fillMode = kCAFillModeForwards;
|
||||
|
||||
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||
toShadowOpacityAnimation.toValue = PearlFloat(0.2);
|
||||
toShadowOpacityAnimation.duration = 0.5f;
|
||||
|
||||
CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||
pulseShadowOpacityAnimation.fromValue = PearlFloat(0.2);
|
||||
pulseShadowOpacityAnimation.toValue = PearlFloat(0.6);
|
||||
pulseShadowOpacityAnimation.beginTime = 0.5f;
|
||||
pulseShadowOpacityAnimation.duration = 2.0f;
|
||||
pulseShadowOpacityAnimation.autoreverses = YES;
|
||||
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
|
||||
|
||||
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
|
||||
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation, nil];
|
||||
group.duration = MAXFLOAT;
|
||||
|
||||
[avatar.layer removeAnimationForKey:@"inactiveShadow"];
|
||||
[avatar.layer addAnimation:group forKey:@"targetedShadow"];
|
||||
}
|
||||
} else {
|
||||
if ([avatar.layer animationForKey:@"targetedShadow"]) {
|
||||
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
|
||||
toShadowColorAnimation.toValue = (__bridge id)[UIColor blackColor].CGColor;
|
||||
toShadowColorAnimation.duration = 0.5f;
|
||||
|
||||
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||
toShadowOpacityAnimation.toValue = PearlFloat(1);
|
||||
toShadowOpacityAnimation.duration = 0.5f;
|
||||
|
||||
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
|
||||
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, nil];
|
||||
group.duration = 0.5f;
|
||||
|
||||
[avatar.layer removeAnimationForKey:@"targetedShadow"];
|
||||
[avatar.layer addAnimation:group forKey:@"inactiveShadow"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (void)textFieldDidBeginEditing:(UITextField *)textField {
|
||||
|
||||
[self setPasswordTip:nil];
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
[textField resignFirstResponder];
|
||||
|
||||
[self setSpinnerActive:YES];
|
||||
|
||||
if (self.selectedUser.keyID)
|
||||
[self tryMasterPassword];
|
||||
|
||||
else
|
||||
[PearlAlert showAlertWithTitle:@"New Master Password"
|
||||
message:@"Please confirm the spelling of this new master password."
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
[self setSpinnerActive:NO];
|
||||
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) {
|
||||
[PearlAlert showAlertWithTitle:@"Incorrect Master Password"
|
||||
message:
|
||||
@"The password you entered doesn't match with the master password you tried to use. "
|
||||
@"You've probably mistyped one of them.\n\n"
|
||||
@"Give it another try."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
|
||||
cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
[self tryMasterPassword];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity
|
||||
targetContentOffset:(inout CGPoint *)targetContentOffset {
|
||||
|
||||
CGFloat xOfMiddle = targetContentOffset->x + scrollView.bounds.size.width / 2;
|
||||
UIButton *middleAvatar = (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, targetContentOffset->y)
|
||||
ofArray:scrollView.subviews];
|
||||
*targetContentOffset = CGPointMake(middleAvatar.center.x - scrollView.bounds.size.width / 2, targetContentOffset->y);
|
||||
|
||||
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
|
||||
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
|
||||
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - IBActions
|
||||
|
||||
- (IBAction)targetedUserAction:(UILongPressGestureRecognizer *)sender {
|
||||
|
||||
if (sender.state != UIGestureRecognizerStateBegan)
|
||||
return;
|
||||
|
||||
if (self.selectedUser)
|
||||
return;
|
||||
|
||||
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]];
|
||||
if (!targetedUser)
|
||||
return;
|
||||
|
||||
[PearlSheet showSheetWithTitle:targetedUser.name
|
||||
message:nil
|
||||
viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
[self updateUsers];
|
||||
} else if (buttonIndex == [sheet firstOtherButtonIndex])
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
@interface MPiOSConfig : MPConfig
|
||||
|
||||
@property (nonatomic, retain) NSNumber *sendInfo;
|
||||
@property (nonatomic, retain) NSNumber *helpHidden;
|
||||
@property (nonatomic, retain) NSNumber *showQuickStart;
|
||||
|
||||
|
||||
@@ -6,27 +6,26 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPiOSConfig.h"
|
||||
|
||||
@implementation MPiOSConfig
|
||||
@dynamic helpHidden, showQuickStart;
|
||||
@dynamic sendInfo, helpHidden, showQuickStart;
|
||||
|
||||
- (id)init {
|
||||
|
||||
if(!(self = [super init]))
|
||||
|
||||
if (!(self = [super init]))
|
||||
return self;
|
||||
|
||||
|
||||
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
|
||||
@"510296984", NSStringFromSelector(@selector(iTunesID)),
|
||||
nil]];
|
||||
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(sendInfo)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
|
||||
@"510296984", NSStringFromSelector(@selector(iTunesID)),
|
||||
nil]];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (MPiOSConfig *)get {
|
||||
|
||||
|
||||
return (MPiOSConfig *)[super get];
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,15 @@
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© 2011-2012, Lyndir</string>
|
||||
<key>ReplacementFonts</key>
|
||||
<dict>
|
||||
<key>Futura-CondensedExtraBold</key>
|
||||
<string>Exo-ExtraBold</string>
|
||||
<key>Futura-CondensedMedium</key>
|
||||
<string>Exo-Light</string>
|
||||
<key>Futura-Medium</key>
|
||||
<string>Exo</string>
|
||||
</dict>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Exo-Black.otf</string>
|
||||
|
||||
@@ -11,12 +11,18 @@
|
||||
#import "Pearl-Prefix.pch"
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
#import "TestFlight.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
#import "TestFlight.h"
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
|
||||
//#define Crashlytics PearlNil
|
||||
//#define TestFlight PearlNil
|
||||
|
||||
#import "MPTypes.h"
|
||||
#import "MPiOSConfig.h"
|
||||
|
||||
#import "MPTypes.h"
|
||||
#import "MPiOSConfig.h"
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<array>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>http://masterpassword.lyndir.com</string>
|
||||
<string>If you're experiencing problems, enabling this will send us details that can help diagnose and resolve them. Great care has been taken to guarantee no private information is ever sent.</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
@@ -42,9 +42,19 @@
|
||||
<key>Key</key>
|
||||
<string>unset</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Send Diagnostic Info</string>
|
||||
<key>Key</key>
|
||||
<string>sendInfo</string>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>This causes your master password to be remembered while your device is powered on. Similar to your phone'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's SIM lock, you only need to log in once after powering on.</string>
|
||||
<key>Title</key>
|
||||
<string>Master Password</string>
|
||||
<key>Type</key>
|
||||
@@ -54,27 +64,9 @@
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
<key>Key</key>
|
||||
<string>rememberKey</string>
|
||||
<string>rememberLogin</string>
|
||||
<key>Title</key>
|
||||
<string>Remember my password</string>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Use this to save your master password in the key chain. This is somewhat less secure should your device get stolen, but it means you won't need to enter the master password anymore. You can compensate for the reduced security by going into General->Passcode Lock, disabling "Simple Passcode", and setting a more secure passcode for your device.</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
<key>Key</key>
|
||||
<string>saveKey</string>
|
||||
<key>Title</key>
|
||||
<string>Save my password</string>
|
||||
<string>Stay logged in</string>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</dict>
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MPAppDelegate class]));
|
||||
}
|
||||
|
||||
BIN
Resources/Avatars/avatar-0.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Resources/Avatars/avatar-0@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-1.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Resources/Avatars/avatar-10.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
Resources/Avatars/avatar-10@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-11.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
Resources/Avatars/avatar-11@2x.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Resources/Avatars/avatar-12.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
Resources/Avatars/avatar-12@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-13.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
Resources/Avatars/avatar-13@2x.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
Resources/Avatars/avatar-14.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Resources/Avatars/avatar-14@2x.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Resources/Avatars/avatar-15.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
Resources/Avatars/avatar-15@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-16.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
Resources/Avatars/avatar-16@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-17.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
Resources/Avatars/avatar-17@2x.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Resources/Avatars/avatar-18.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
Resources/Avatars/avatar-18@2x.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Resources/Avatars/avatar-1@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-2.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Resources/Avatars/avatar-2@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-3.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Resources/Avatars/avatar-3@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-4.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Resources/Avatars/avatar-4@2x.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Resources/Avatars/avatar-5.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
Resources/Avatars/avatar-5@2x.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Resources/Avatars/avatar-6.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
Resources/Avatars/avatar-6@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Resources/Avatars/avatar-7.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |