Compare commits
36 Commits
1.5-appsto
...
1.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07dd98823f | ||
|
|
40d6019f71 | ||
|
|
32ec0038b6 | ||
|
|
99a1d505d3 | ||
|
|
62076d8170 | ||
|
|
009b1ff996 | ||
|
|
27870c7420 | ||
|
|
7a16b47e37 | ||
|
|
48324735e1 | ||
|
|
f0dcc4c34c | ||
|
|
36386c3213 | ||
|
|
8ea0f00cf0 | ||
|
|
0921796136 | ||
|
|
ab360066e5 | ||
|
|
34645c9433 | ||
|
|
b2a608824c | ||
|
|
af56920fd9 | ||
|
|
784b741b60 | ||
|
|
ea066bd828 | ||
|
|
c5d88bd5c7 | ||
|
|
a8115fdced | ||
|
|
c0406888fc | ||
|
|
c7f04f8449 | ||
|
|
ae4be86ecf | ||
|
|
d188985823 | ||
|
|
ac0d412123 | ||
|
|
19e35dbccd | ||
|
|
3086baf523 | ||
|
|
e3d8d3c31c | ||
|
|
7589b3a048 | ||
|
|
72f0d69a71 | ||
|
|
2399156ee4 | ||
|
|
3d3f08da9b | ||
|
|
2a0abf0da7 | ||
|
|
2d8146edbd | ||
|
|
dad198f5e1 |
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -9,6 +9,7 @@
|
||||
<inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnavailableInDeploymentTarget" enabled="true" level="INFO" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
|
||||
2
External/InAppSettingsKit
vendored
28
External/Localytics/LocalyticsSession.h
vendored
@@ -13,6 +13,9 @@
|
||||
#define CLIENT_VERSION @"2.18.0"
|
||||
#define MARKETING_PLATFORM
|
||||
|
||||
// Forward declaration
|
||||
@protocol LocalyticsSessionDelegate;
|
||||
|
||||
/*!
|
||||
@class LocalyticsSession
|
||||
@discussion The class which manages creating, collecting, & uploading a Localytics session.
|
||||
@@ -40,10 +43,6 @@
|
||||
|
||||
@author Localytics
|
||||
*/
|
||||
|
||||
// Forward declaration
|
||||
@protocol LocalyticsSessionDelegate;
|
||||
|
||||
@interface LocalyticsSession : NSObject
|
||||
|
||||
|
||||
@@ -65,7 +64,7 @@
|
||||
|
||||
/*!
|
||||
@property sessionTimeoutInterval
|
||||
@abstrac (Optional) If an App stays in the background for more than this many seconds,
|
||||
@abstract (Optional) If an App stays in the background for more than this many seconds,
|
||||
start a new session when it returns to foreground.
|
||||
*/
|
||||
@property (atomic) float sessionTimeoutInterval;
|
||||
@@ -98,7 +97,7 @@
|
||||
upload methods. Best Practice is to call open & upload immediately after Localytics Session when loading an app,
|
||||
this method fascilitates that behavior.
|
||||
It is recommended that this call be placed in <code>applicationDidFinishLaunching</code>.
|
||||
@param applicationKey The key unique for each application generated
|
||||
@param appKey The key unique for each application generated
|
||||
at www.localytics.com
|
||||
*/
|
||||
- (void)startSession:(NSString *)appKey;
|
||||
@@ -159,20 +158,16 @@
|
||||
<br>
|
||||
See the tagging guide at: http://wiki.localytics.com/
|
||||
@param event The name of the event which occurred.
|
||||
@param attributes (Optional) An object/hash/dictionary of key-value pairs, contains
|
||||
@param attributes (Optional) An object/hash/dictionary of key-value pairs, contains
|
||||
contextual data specific to the event.
|
||||
@param rerportAttributes (Optional) Additional attributes used for custom reporting.
|
||||
@param reportAttributes (Optional) Additional attributes used for custom reporting.
|
||||
Available to Enterprise customers, please contact services for more details.
|
||||
@param customerValueIncrease (Optional) Numeric value, added to customer lifetime value.
|
||||
Integer expected. Try to use lowest possible unit, such as cents for US currency.
|
||||
*/
|
||||
- (void)tagEvent:(NSString *)event;
|
||||
|
||||
- (void)tagEvent:(NSString *)event
|
||||
attributes:(NSDictionary *)attributes;
|
||||
|
||||
- (void)tagEvent:(NSString *)event
|
||||
attributes:(NSDictionary *)attributes
|
||||
reportAttributes:(NSDictionary *)reportAttributes
|
||||
customerValueIncrease:(NSNumber *)customerValueIncrease;
|
||||
|
||||
- (void)tagEvent:(NSString *)event
|
||||
@@ -181,9 +176,12 @@ reportAttributes:(NSDictionary *)reportAttributes;
|
||||
|
||||
- (void)tagEvent:(NSString *)event
|
||||
attributes:(NSDictionary *)attributes
|
||||
reportAttributes:(NSDictionary *)reportAttributes
|
||||
customerValueIncrease:(NSNumber *)customerValueIncrease;
|
||||
|
||||
- (void)tagEvent:(NSString *)event
|
||||
attributes:(NSDictionary *)attributes;
|
||||
|
||||
- (void)tagEvent:(NSString *)event;
|
||||
|
||||
/*!
|
||||
@method tagScreen
|
||||
@@ -205,7 +203,7 @@ customerValueIncrease:(NSNumber *)customerValueIncrease;
|
||||
/*!
|
||||
@method LocalyticsSession
|
||||
@abstract Initializes the Localytics Object. Not necessary if you choose to use startSession.
|
||||
@param applicationKey The key unique for each application generated at www.localytics.com
|
||||
@param appKey The key unique for each application generated at www.localytics.com
|
||||
*/
|
||||
- (void)LocalyticsSession:(NSString *)appKey;
|
||||
|
||||
|
||||
2
External/Pearl
vendored
244
External/TestFlight/README.md
vendored
@@ -1,30 +1,26 @@
|
||||
##Introduction
|
||||
## Introduction
|
||||
|
||||
The TestFlight SDK allows you to track how beta testers are testing your application. Out of the box we track simple usage information, such as which tester is using your application, their device model/OS, how long they used the application, logs of their test session, and automatic recording of any crashes they encounter.
|
||||
The TestFlight SDK allows you to track how beta testers are testing your application. Out of the box we track simple usage information, such as which tester is using your application, their device model/OS, how long they used the application, and automatic recording of any crashes they encounter.
|
||||
|
||||
To get the most out of the SDK we have provided the Checkpoint API.
|
||||
The SDK can track more information if you pass it to TestFlight. The Checkpoint API is used to help you track exactly how your testers are using your application. Curious about which users passed level 5 in your game, or posted their high score to Twitter, or found that obscure feature? See "Checkpoint API" down below to see how.
|
||||
|
||||
The Checkpoint API is used to help you track exactly how your testers are using your application. Curious about which users passed level 5 in your game, or posted their high score to Twitter, or found that obscure feature? With a single line of code you can finally gather all this information. Wondering how many times your app has crashed? Wondering who your power testers are? We've got you covered. See more information on the Checkpoint API in section 4.
|
||||
The SDK also offers a remote logging solution. Find out more about our logging system in the "Remote Logging" section.
|
||||
|
||||
Alongside the Checkpoint API is the Questions interface. The Questions interface is managed on a per build basis on the TestFlight website. Find out more about the Questions Interface in section 6.
|
||||
## Requirements
|
||||
|
||||
For more detailed debugging we have a remote logging solution. Find out more about our logging system with TFLog in the Remote Logging section.
|
||||
The TestFlight SDK requires iOS 4.3 or above, the Apple LLVM compiler, and the libz library to run.
|
||||
|
||||
|
||||
##Considerations
|
||||
|
||||
Information gathered by the SDK is sent to the website in real time. When an application is put into the background (iOS 4.x) or terminated (iOS 3.x) we try to send the finalizing information for the session during the time allowed for finalizing the application. Should all of the data not get sent the remaining data will be sent the next time the application is launched. As such, to get the most out of the SDK we recommend your application support iOS 4.0 and higher.
|
||||
|
||||
This SDK can be run from both the iPhone Simulator and Device and has been tested using Xcode 4.0.
|
||||
The AdSupport.framework is required for iOS 6.0+ in order to uniquely identify users so we can estimate the number of users your app has (using `ASIdentifierManager`). You may weak link the framework in you app. If your app does not link with the AdSupport.framework, the TestFlight SDK will automatically load it for apps running on iOS 6.0+.
|
||||
|
||||
|
||||
##Integration
|
||||
## Integration
|
||||
|
||||
1. Add the files to your project: File -> Add Files to " "
|
||||
1. Add the files to your project: File -> Add Files to " "
|
||||
1. Find and select the folder that contains the SDK
|
||||
2. Make sure that "Copy items into destination folder (if needed)" is checked
|
||||
3. Set Folders to "Create groups for any added folders"
|
||||
4. Select all targets that you want to add the SDK to
|
||||
|
||||
2. Verify that libTestFlight.a has been added to the Link Binary With Libraries Build Phase for the targets you want to use the SDK with
|
||||
1. Select your Project in the Project Navigator
|
||||
2. Select the target you want to enable the SDK for
|
||||
@@ -32,6 +28,7 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
|
||||
4. Open the Link Binary With Libraries Phase
|
||||
5. If libTestFlight.a is not listed, drag and drop the library from your Project Navigator to the Link Binary With Libraries area
|
||||
6. Repeat Steps 2 - 5 until all targets you want to use the SDK with have the SDK linked
|
||||
|
||||
3. Add libz to your Link Binary With Libraries Build Phase
|
||||
1. Select your Project in the Project Navigator
|
||||
2. Select the target you want to enable the SDK for
|
||||
@@ -41,95 +38,158 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
|
||||
6. Find libz.dylib in the list and add it
|
||||
7. Repeat Steps 2 - 6 until all targets you want to use the SDK with have libz.dylib
|
||||
|
||||
4. In your Application Delegate:
|
||||
1. Import TestFlight: `#import "TestFlight.h"`
|
||||
4. Get your App Token
|
||||
|
||||
***NOTE:*** Rather than importing `TestFlight.h` in every file you may add the above line into you pre-compiled header (`<projectname>_Prefix.pch`) file inside of the
|
||||
1. If this is a new application, and you have not uploaded it to TestFlight before, first register it here: [https://testflightapp.com/dashboard/applications/create/]().
|
||||
|
||||
Otherwise, if you have previously uploaded your app to TestFlight, go to your list of applications ([http://testflightapp.com/dashboard/applications/]()) and click on the application you are using from the list.
|
||||
|
||||
2. Click on the "App Token" tab on the left. The App Token for that application will be there.
|
||||
|
||||
5. In your Application Delegate:
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
|
||||
section. This will give you access to the SDK across all files.
|
||||
|
||||
2. Get your Application Token which you can find at [http://testflightapp.com/dashboard/applications/](http://testflightapp.com/dashboard/applications/) select the application you are using from the list choose the SDK option and the application token for this application will be there. To ensure that your testers do not show up as anonymous place the call to setDeviceIdentifer before calling takeOff. Remove #define TESTING 1 before building your release build for the App Store.
|
||||
|
||||
3. Launch TestFlight with your Application Token
|
||||
1. Import TestFlight: `#import "TestFlight.h"`
|
||||
|
||||
2. Launch TestFlight with your App Token
|
||||
|
||||
In your `-application:didFinishLaunchingWithOptions:` method, call `+[TestFlight takeOff:]` with your App Token.
|
||||
|
||||
-(BOOL)application:(UIApplication *)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
// start of your application:didFinishLaunchingWithOptions
|
||||
// start of your application:didFinishLaunchingWithOptions
|
||||
|
||||
// !!!: Use the next line only during beta
|
||||
// [TestFlight setDeviceIdentifier:[[UIDevice currentDevice] uniqueIdentifier]];
|
||||
|
||||
[TestFlight takeOff:@"Insert your Application Token here"];
|
||||
// The rest of your application:didFinishLaunchingWithOptions method
|
||||
// ...
|
||||
[TestFlight takeOff:@"Insert your Application Token here"];
|
||||
|
||||
// The rest of your application:didFinishLaunchingWithOptions method
|
||||
// ...
|
||||
}
|
||||
|
||||
4. To report crashes to you we install our own uncaught exception handler. If you are not currently using an exception handler of your own then all you need to do is go to the next step. If you currently use an Exception Handler, or you use another framework that does please go to the section on advanced exception handling.
|
||||
3. To report crashes to you we install our own uncaught exception handler. If you are not currently using an exception handler of your own then all you need to do is go to the next step. If you currently use an Exception Handler, or you use another framework that does please go to the section on advanced exception handling.
|
||||
|
||||
|
||||
##Beta Testing and Release Differentiation
|
||||
## Setting the UDID
|
||||
|
||||
In order to provide more information about your testers while beta testing you will need to provide the device's unique identifier. This identifier is not something that the SDK will collect from the device and we do not recommend using this in production. To send the device identifier to us put the following code **before your call to takeOff**.
|
||||
For **BETA** apps only: In order for "In App Updates" to work and for user data not to be anonymized, you may provide the device's unique identifier. To send the device identifier call the following method **before** your call to `+[TestFlight takeOff:]` like so:
|
||||
|
||||
[TestFlight setDeviceIdentifier:[[UIDevice currentDevice] uniqueIdentifier]];
|
||||
[TestFlight takeOff:@"Insert your Application Token here"];
|
||||
|
||||
This will allow you to have the best possible information during testing. **When it is time to submit to the App Store comment this line out**. Apple may reject your app if you leave this line in. If you decide to not include the device's unique identifier during your testing phase TestFlight will still collect all of the information that you send but it may be anonymized.
|
||||
|
||||
|
||||
##Checkpoint API
|
||||
Note: `[[UIDevice currentDevice] uniqueIdentifier]` is deprecated, which means it may be removed from iOS in the future and that it should not be used in production apps. We recommend using it **only** in beta apps. If using it makes you feel uncomfortable, you are not required to include it.
|
||||
|
||||
When a tester does something you care about in your app you can pass a checkpoint. For example completing a level, adding a todo item, etc. The checkpoint progress is used to provide insight into how your testers are testing your apps. The passed checkpoints are also attached to crashes, which can help when creating steps to replicate.
|
||||
**Note on iOS 7 and Xcode 5**: In iOS 7, `uniqueIdentifier` no longer returns the device's UDID, so iOS 7 users will show up anonymously on TestFlight. Also, when building with ARC, Xcode 5 will not allow you to call `uniqueIdentifier` because it has been removed in iOS 7 from `UIDevice`'s header. We are working on a workaround for this issue.
|
||||
|
||||
`[TestFlight passCheckpoint:@"CHECKPOINT_NAME"];` Use `passCheckpoint:` to track when a user performs certain tasks in your application. This can be useful for making sure testers are hitting all parts of your application, as well as tracking which testers are being thorough.
|
||||
**DO NOT USE THIS IN PRODUCTION APPS**. When it is time to submit to the App Store comment this line out. Apple will probably reject your app if you leave this line in.
|
||||
|
||||
##Feedback API
|
||||
|
||||
To launch unguided feedback call the openFeedbackView method. We recommend that you call this from a GUI element.
|
||||
|
||||
-(IBAction)launchFeedback {
|
||||
[TestFlight openFeedbackView];
|
||||
}
|
||||
## Uploading your build
|
||||
|
||||
After you have integrated the SDK into your application you need to upload your build to TestFlight. You can upload your build on our [website](https://testflightapp.com/dashboard/builds/add/), using our [desktop app](https://testflightapp.com/desktop/), or by using our [upload API](https://testflightapp.com/api/doc/).
|
||||
|
||||
If you want to create your own feedback form you can use the submitCustomFeedback method to submit the feedback that the user has entered.
|
||||
|
||||
-(IBAction)submitFeedbackPressed:(id)sender {
|
||||
NSString *feedback = [self getUserFeedback];
|
||||
[TestFlight submitFeedback:feedback];
|
||||
}
|
||||
## Basic Features
|
||||
|
||||
The above sample assumes that [self getUserFeedback] is implemented such that it obtains the users feedback from the GUI element you have created and that submitFeedbackPressed is the action for your submit button.
|
||||
### Session Information
|
||||
|
||||
View anonymous information about how often users use your app, how long they use it for, and when they use it. You can see what type of device the user is using, which OS, which language, etc.
|
||||
|
||||
Sessions automatically start at app launch, app did become active, and app will enter foreground and end at app will resign active, app did enter background, or app will terminate. Sessions that start shortly after an end continue the session instead of starting a new one.
|
||||
|
||||
For **beta** users, you can see who the users are if you are **setting the UDID**, they have a TestFlight account, and their device is registered to TestFlight. (See Setting the UDID for more information).
|
||||
|
||||
|
||||
### Crash Reports
|
||||
|
||||
The TestFlight SDK automatically reports all crashes (beta and prod) to TestFlight's website where you can view them. Crash reports are sent **at** crash time. TestFlight will also automatically symbolicate all crashes (if you have uploaded your dSYM). For **beta** apps, on the site, you can see which checkpoints the user passed before the crash and see remote logs that were sent before the crash. For **prod** apps, you can see remote logs that were sent before the crash.
|
||||
|
||||
|
||||
### Beta In App Updates
|
||||
|
||||
If a user is using a **beta** version of your app, you are **setting the UDID**, a new beta version is available, and that user has permission to install it; an in app popup will ask them if they would like to install the update. If they tap "Install", the new version is installed from inside the app.
|
||||
|
||||
NB: For this to work, you must increment your build version before uploading. Otherwise the new and old builds will have the same version number and we won't know if the user needs to update or is already using the new version.
|
||||
|
||||
To turn this off set this option before calling `takeOff:`
|
||||
|
||||
[TestFlight setOptions:@{ TFOptionDisableInAppUpdates : @YES }];
|
||||
|
||||
|
||||
## Additional Features
|
||||
|
||||
### Checkpoints
|
||||
|
||||
When a tester does something you care about in your app, you can pass a checkpoint. For example completing a level, adding a todo item, etc. The checkpoint progress is used to provide insight into how your testers are testing your apps. The passed checkpoints are also attached to crashes, which can help when creating steps to replicate. Checkpoints are visible for all beta and prod builds.
|
||||
|
||||
[TestFlight passCheckpoint:@"CHECKPOINT_NAME"];
|
||||
|
||||
Use `passCheckpoint:` to track when a user performs certain tasks in your application. This can be useful for making sure testers are hitting all parts of your application, as well as tracking which testers are being thorough.
|
||||
|
||||
Checkpoints are meant to tell you if a user visited a place in your app or completed a task. They should not be used for debugging purposes. Instead, use Remote Logging for debugging information (more information below).
|
||||
|
||||
|
||||
### Custom Environment Information
|
||||
|
||||
In **beta** builds, if you want to see some extra information about your user, you can add some custom environment information. You must add this information before the session starts (a session starts at `takeOff:`) to see it on TestFlight's website. NB: You can only see this information for **beta** users.
|
||||
|
||||
[TestFlight addCustomEnvironmentInformation:@"info" forKey:@"key"];
|
||||
|
||||
You may call this method as many times as you would like to add more information.
|
||||
|
||||
|
||||
### User Feedback
|
||||
|
||||
In **beta** builds, if you collect feedback from your users, you may pass it back to TestFlight which will associate it with the user's current session.
|
||||
|
||||
[TestFlight submitFeedback:feedback];
|
||||
|
||||
Once users have submitted feedback from inside of the application you can view it in the feedback area of your build page.
|
||||
|
||||
|
||||
##Upload your build
|
||||
### Remote Logging
|
||||
|
||||
Remote Logging allows you to see the logs your app prints out remotely, on TestFlight's website. You can see logs for **beta sessions** and **prod sessions with crashes**. NB: you cannot see the logs for all prod sessions.
|
||||
|
||||
To use it, simply replace all of your `NSLog` calls with `TFLog` calls. An easy way to do this without rewriting all your `NSLog` calls is to add the following macro to your `.pch` file.
|
||||
|
||||
#import "TestFlight.h"
|
||||
#define NSLog TFLog
|
||||
|
||||
Not only will `TFLog` log remotely to TestFlight, it will also log to the console (viewable in a device's logs) and STDERR (shown while debugging) just like NSLog does, providing a complete replacement.
|
||||
|
||||
For even better information in your remote logs, such as file name and line number, you can use this macro instead:
|
||||
|
||||
#define NSLog(__FORMAT__, ...) TFLog((@"%s [Line %d] " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
Which will produce output that looks like
|
||||
|
||||
-[MyAppDelegate application:didFinishLaunchingWithOptions:] [Line 45] Launched!
|
||||
|
||||
**Custom Logging**
|
||||
|
||||
If you have your own custom logging, call `TFLog` from your custom logging function. If you do not need `TFLog` to log to the console or STDERR because you handle those yourself, you can turn them off with these calls:
|
||||
|
||||
[TestFlight setOptions:@{ TFOptionLogToConsole : @NO }];
|
||||
[TestFlight setOptions:@{ TFOptionLogToSTDERR : @NO }];
|
||||
|
||||
After you have integrated the SDK into your application you need to upload your build to TestFlight. You can upload from your dashboard or or using the Upload API, full documentation at [https://testflightapp.com/api/doc/](https://testflightapp.com/api/doc/)
|
||||
## Advanced Notes
|
||||
|
||||
##Questions Interface
|
||||
### Checkpoint API
|
||||
|
||||
In order to ask a question, you'll need to associate it with a checkpoint. Make sure your checkpoints are initialized by running your app and hitting them all yourself before you start adding questions.
|
||||
When passing a checkpoint, TestFlight logs the checkpoint synchronously (See Remote Logging for more information). If your app has very high performance needs, you can turn the logging off with the `TFOptionLogOnCheckpoint` option.
|
||||
|
||||
There are three question types available: Yes/No, Multiple Choice, and Long Answer.
|
||||
|
||||
To create questions, visit your builds Questions page and click on 'Add Question'. If you choose Multiple Choice, you'll need to enter a list of possible answers for your testers to choose from — otherwise, you'll only need to enter your question's, well, question. If your build has no questions, you can also choose to migrate questions from another build (because seriously — who wants to do all that typing again)?
|
||||
### Remote Logging
|
||||
|
||||
After restarting your application on an approved device, when you pass the checkpoint associated with your questions a TestFlight modal question form will appear on the screen asking the beta tester to answer your question.
|
||||
All logging is done synchronously. Every time the SDK logs, it must write data to a file. This is to ensure log integrity at crash time. Without this, we could not trust logs at crash time. If you have a high performance app, please email support@testflightapp.com for more options.
|
||||
|
||||
After you upload a new build to TestFlight you will need to associate questions once again. However if your checkpoints and questions have remained the same you can choose "copy questions from an older build" and choose which build to copy the questions from.
|
||||
### Advanced Session Control
|
||||
|
||||
##View the results
|
||||
|
||||
As testers install your build and start to test it you will see their session data on the web on the build report page for the build you've uploaded.
|
||||
Continuing sessions: You can adjust the amount of time a user can leave the app for and still continue the same session when they come back by changing the `TFOptionSessionKeepAliveTimeout` option. Change it to 0 to turn the feature off.
|
||||
|
||||
##Advanced Exception Handling
|
||||
Manual Session Control: If your app is a music player that continues to play music in the background, a navigation app that continues to function in the background, or any app where a user is considered to be "using" the app even while the app is not active you should use Manual Session Control. Please only use manual session control if you know exactly what you are doing. There are many pitfalls which can result in bad session duration and counts. See `TestFlight+ManualSessions.h` for more information and instructions.
|
||||
|
||||
An uncaught exception means that your application is in an unknown state and there is not much that you can do but try and exit gracefully. Our SDK does its best to get the data we collect in this situation to you while it is crashing, but it is designed in such a way that the important act of saving the data occurs in as safe way a way as possible before trying to send anything. If you do use uncaught exception or signal handlers install your handlers before calling `takeOff`. Our SDK will then call your handler while ours is running. For example:
|
||||
### Advanced Exception/Signal Handling
|
||||
|
||||
An uncaught exception means that your application is in an unknown state and there is not much that you can do but try and exit gracefully. Our SDK does its best to get the data we collect in this situation to you while it is crashing, but it is designed in such a way that the important act of saving the data occurs in as safe way a way as possible before trying to send anything. If you do use uncaught exception or signal handlers, install your handlers before calling `takeOff:`. Our SDK will then call your handler while ours is running. For example:
|
||||
|
||||
/*
|
||||
My Apps Custom uncaught exception catcher, we do special stuff here, and TestFlight takes care of the rest
|
||||
@@ -167,55 +227,3 @@ An uncaught exception means that your application is in an unknown state and the
|
||||
|
||||
You do not need to add the above code if your application does not use exception handling already.
|
||||
|
||||
##Remote Logging
|
||||
|
||||
To perform remote logging you can use the TFLog method which logs in a few different methods described below. In order to make the transition from NSLog to TFLog easy we have used the same method signature for TFLog as NSLog. You can easily switch over to TFLog by adding the following macro to your header
|
||||
|
||||
#define NSLog TFLog
|
||||
|
||||
That will do a switch from NSLog to TFLog, if you want more information, such as file name and line number you can use a macro like
|
||||
|
||||
#define NSLog(__FORMAT__, ...) TFLog((@"%s [Line %d] " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
Which will produce output that looks like
|
||||
|
||||
-[HTFCheckpointsController showYesNoQuestion:] [Line 45] Pressed YES/NO
|
||||
|
||||
We have implemented three different loggers.
|
||||
|
||||
1. TestFlight logger
|
||||
2. Apple System Log logger
|
||||
3. STDERR logger
|
||||
|
||||
Each of the loggers log asynchronously and all TFLog calls are non blocking. The TestFlight logger writes its data to a file which is then sent to our servers on Session End events. The Apple System Logger sends its messages to the Apple System Log and are viewable using the Organizer in Xcode when the device is attached to your computer. The ASL logger can be disabled by turning it off in your TestFlight options
|
||||
|
||||
[TestFlight setOptions:{ TFOptionLogToConsole : @NO }];
|
||||
|
||||
The default option is YES.
|
||||
|
||||
The STDERR logger sends log messages to STDERR so that you can see your log statements while debugging. The STDERR logger is only active when a debugger is attached to your application. If you do not wish to use the STDERR logger you can disable it by turning it off in your TestFlight options
|
||||
|
||||
[TestFlight setOptions:{ TFOptionLogToSTDERR : @NO }];
|
||||
|
||||
The default option is YES.
|
||||
|
||||
## Advanced Remote Logging
|
||||
|
||||
For most users we expect using TFLog to provide all of the logging functionality that they need. For the occasion where you need to provide a wrapper around TFLog we provide
|
||||
|
||||
void TFLogv(NSString *format, va_list arg_list);
|
||||
|
||||
Using TFLogv you can have your method that accepts a variable number of arguments that then passes that format and argument list to TFLog.
|
||||
|
||||
|
||||
##iOS3
|
||||
|
||||
We now require that anyone who is writing an application that supports iOS3 add the System.framework as an optional link. In order to provide a better shutdown experience we send any large log files to our servers in the background. To add System.framework as an optional link:
|
||||
|
||||
1. Select your Project in the Project Navigator
|
||||
2. Select the target you want to enable the SDK for
|
||||
3. Select the Build Phases tab
|
||||
4. Open the Link Binary With Libraries Phase
|
||||
5. Click the + to add a new library
|
||||
6. Find libSystem.dylib in the list and add it
|
||||
7. To the right of libSystem.dylib in the Link Binary With Libraries pane change "Required" to "Optional"
|
||||
|
||||
30
External/TestFlight/TestFlight+AsyncLogging.h
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// TestFlight+AsyncLogging.h
|
||||
// libTestFlight
|
||||
//
|
||||
// Created by Jason Gregori on 2/12/13.
|
||||
// Copyright (c) 2013 TestFlight. All rights reserved.
|
||||
//
|
||||
|
||||
/*
|
||||
|
||||
When logging, it is important that logs are written synchronously. In the event of a crash, all logs that happened before the crash are gauranteed to be on disk. If they were written asynchronously and a crash occurs, you might lose some very valuable logs that might have helped fixed the crash.
|
||||
|
||||
However, because TFLog waits until writing to disk is complete, it takes a while. If you have a very high preformance app that can't afford to wait for logs, these functions are for you.
|
||||
|
||||
USE THESE, BUT KNOW YOU RISK LOSING SOME LOGS AT CRASH TIME
|
||||
|
||||
*/
|
||||
|
||||
#import "TestFlight.h"
|
||||
|
||||
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void TFLog_async(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
|
||||
void TFLogv_async(NSString *format, va_list arg_list);
|
||||
#if __cplusplus
|
||||
}
|
||||
#endif
|
||||
58
External/TestFlight/TestFlight+ManualSessions.h
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// TestFlight+ManualSessions.h
|
||||
// libTestFlight
|
||||
//
|
||||
// Created by Jason Gregori on 5/16/13.
|
||||
// Copyright (c) 2013 TestFlight. All rights reserved.
|
||||
//
|
||||
|
||||
/*
|
||||
|
||||
YOU ARE STRONGLY ADVISED NOT TO USE THESE METHODS unless you know exactly what you are doing. By using these you take on the responsibility of ensuring your session data is reported accurately.
|
||||
|
||||
The way TestFlight normally does sessions is to automatically start them at app launch, app did become active, and app will enter foreground and end them at app will resign active, app did enter background, or app will terminate.
|
||||
|
||||
If your app is a music player that continues to play music in the background, a navigation app that continues to function in the background, or any app where a user is considered to be "using" the app even while the app is not active, this file is for you.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
1. Add this file to your project.
|
||||
|
||||
2. Set the manual sessions option to true **before** calling `takeOff:`
|
||||
|
||||
[TestFlight setOptions:@{ TFOptionManualSessions : @YES }];
|
||||
|
||||
3. Use the manually start/end session methods to control you sessions.
|
||||
|
||||
|
||||
Pitfalls
|
||||
--------
|
||||
|
||||
When using manual sessions in the background, you must always be aware of the fact that iOS may suspend your app at any time without any warning. You must end your session before that happens. If you do not, the session will continue and include all the time the app was suspended in it's duration if the app is brought back from suspension. This will lead to very inaccurate session lengths and counts.
|
||||
|
||||
On app termination: For the most accurate sessions, try to end your session if you know the app is about to terminate. If you do not, the session will still be ended on the next launch, however, it's end time will not be exact. In that case, the end time will be within 30 seconds of the correct time (session information is saved every 30 seconds and when a checkpoint is sent).
|
||||
|
||||
Sessions do not continue across termination if you do not end a session before termination.
|
||||
|
||||
On crashes: Do not worry about ending sessions in the event of a crash. Even manual sessions are automatically ended in the event of a crash.
|
||||
|
||||
Continuing sessions: If a session is started without 30 seconds of the last session ending (and their was no termination between the sessions), the last session will continue instead of a new session starting. This is the case in manual and automatic sessions. You may change the timeout or turn this feature off using the `TFOptionSessionKeepAliveTimeout` option.
|
||||
|
||||
*/
|
||||
|
||||
#import "TestFlight.h"
|
||||
|
||||
|
||||
|
||||
extern NSString *const TFOptionManualSessions; // Defaults to @NO. Set to @YES before calling `takeOff:` in order to use manual session methods.
|
||||
|
||||
|
||||
@interface TestFlight (ManualSessions)
|
||||
|
||||
// these methods are thread safe
|
||||
+ (void)manuallyStartSession;
|
||||
+ (void)manuallyEndSession;
|
||||
|
||||
@end
|
||||
31
External/TestFlight/TestFlight.h
vendored
@@ -6,14 +6,19 @@
|
||||
// Copyright 2011 TestFlight. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#define TESTFLIGHT_SDK_VERSION @"1.2.4"
|
||||
#define TESTFLIGHT_SDK_VERSION @"2.0.0"
|
||||
#undef TFLog
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void TFLog(NSString *format, ...);
|
||||
/*
|
||||
* Remote Logging
|
||||
* Note: All Logging is synchronous, see the README for more information.
|
||||
*/
|
||||
void TFLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
|
||||
void TFLogv(NSString *format, va_list arg_list);
|
||||
void TFLogPreFormatted(NSString *message);
|
||||
#if __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -22,13 +27,12 @@ extern "C" {
|
||||
* TestFlight object
|
||||
* All methods are class level
|
||||
*/
|
||||
@interface TestFlight : NSObject {
|
||||
|
||||
}
|
||||
@interface TestFlight : NSObject
|
||||
|
||||
/**
|
||||
* Add custom environment information
|
||||
* If you want to track custom information such as a user name from your application you can add it here
|
||||
* If you want to track custom information such as a user name from your application you can add it here.
|
||||
* NB: This information must be added before the session starts, it is recorded only on session start.
|
||||
*
|
||||
* @param information A string containing the environment you are storing
|
||||
* @param key The key to store the information with
|
||||
@@ -43,7 +47,6 @@ extern "C" {
|
||||
* The token for this application can be retrieved by going to https://testflightapp.com/dashboard/applications/
|
||||
* selecting this application from the list then selecting SDK.
|
||||
*/
|
||||
|
||||
+ (void)takeOff:(NSString *)applicationToken;
|
||||
|
||||
/**
|
||||
@@ -55,17 +58,14 @@ extern "C" {
|
||||
+ (void)setOptions:(NSDictionary*)options;
|
||||
|
||||
/**
|
||||
* Track when a user has passed a checkpoint after the flight has taken off. Eg. passed level 1, posted high score
|
||||
* Track when a user has passed a checkpoint after the flight has taken off. Eg. passed level 1, posted high score.
|
||||
* Checkpoints are sent in the background.
|
||||
* Note: The checkpoint is logged synchronously (See TFLog and TFOptionLogOnCheckpoint for more information).
|
||||
*
|
||||
* @param checkpointName The name of the checkpoint, this should be a static string
|
||||
*/
|
||||
+ (void)passCheckpoint:(NSString *)checkpointName;
|
||||
|
||||
/**
|
||||
* Opens a feedback window that is not attached to a checkpoint
|
||||
*/
|
||||
+ (void)openFeedbackView;
|
||||
|
||||
/**
|
||||
* Submits custom feedback to the site. Sends the data in feedback to the site. This is to be used as the method to submit
|
||||
* feedback from custom feedback forms.
|
||||
@@ -105,10 +105,13 @@ extern "C" {
|
||||
* Pass these as keys to the dictionary you pass to +`[TestFlight setOptions:]`.
|
||||
* The values should be NSNumber BOOLs (`[NSNumber numberWithBool:YES]` or `@YES`)
|
||||
*/
|
||||
extern NSString *const TFOptionAttachBacktraceToFeedback; // Defaults to @NO. Setting to @YES attaches the current backtrace, with symbols, to the feedback.
|
||||
extern NSString *const TFOptionDisableInAppUpdates; // Defaults to @NO. Setting to @YES, disables the in app update screen shown in BETA apps when there is a new version available on TestFlight.
|
||||
extern NSString *const TFOptionFlushSecondsInterval; // Defaults to @60. Set to a number. @0 turns off the flush timer. 30 seconds is the minimum flush interval.
|
||||
extern NSString *const TFOptionLogOnCheckpoint; // Defaults to @YES. Because logging is synchronous, if you have a high preformance app, you might want to turn this off.
|
||||
extern NSString *const TFOptionLogToConsole; // Defaults to @YES. Prints remote logs to Apple System Log.
|
||||
extern NSString *const TFOptionLogToSTDERR; // Defaults to @YES. Sends remote logs to STDERR when debugger is attached.
|
||||
extern NSString *const TFOptionReinstallCrashHandlers; // If set to @YES: Reinstalls crash handlers, to be used if a third party library installs crash handlers overtop of the TestFlight Crash Handlers.
|
||||
extern NSString *const TFOptionReportCrashes; // Defaults to @YES. If set to @NO, crash handlers are never installed. Must be set **before** calling `takeOff:`.
|
||||
extern NSString *const TFOptionSendLogOnlyOnCrash; // Defaults to @NO. Setting to @YES stops remote logs from being sent when sessions end. They would only be sent in the event of a crash.
|
||||
extern NSString *const TFOptionSessionKeepAliveTimeout; // Defaults to @30. This is the amount of time a user can leave the app for and still continue the same session when they come back. If they are away from the app for longer, a new session is created when they come back. Must be a number. Change to @0 to turn off.
|
||||
|
||||
|
||||
BIN
External/TestFlight/libTestFlight.a
vendored
36
External/TestFlight/release_notes.md
vendored
@@ -1,3 +1,37 @@
|
||||
## 2.0
|
||||
|
||||
Improvements
|
||||
|
||||
- ARC
|
||||
- All public TestFlight methods may be called from any thread or dispatch_queue
|
||||
- All public TestFlight methods (except for `TFLog` and `takeOff:`) are asynchronous, so there is never a wait on them
|
||||
- TestFlight never uses more than 1 network connection at a time
|
||||
- All network traffic is grouped together, sent at once, and transferred in MessagePack. This results in using less bandwidth and less network calls.
|
||||
- All network traffic if server is not reachable
|
||||
- Size of SDK reduced by 70%
|
||||
- New In App Update UI in an alert with landscape support. Should work for all different types of apps.
|
||||
- Manual Sessions: You can manually control session start and end. See `TestFlight+ManualSessions.h` for more information
|
||||
- Combining of back to back sessions. If a session starts less than 30 seconds from the last session which ended, the previous session is continued. You may change the time limit (or turn this off) using the `TFOptionSessionKeepAliveTimeout` option key.
|
||||
- No longer automatically starts a session on `+takeOff:` in order to support new background modes that might launch an app in the background.
|
||||
- `TFOptionReportCrashes` option to not install crash handlers
|
||||
- Remove all calls to `dispatch_get_current_queue`, it is deprecated
|
||||
|
||||
Changes
|
||||
|
||||
- Removed all access to mac address
|
||||
- Added AdSupport.framework requirement (as a replacement for mac address to get accurate user counts)
|
||||
- Add format attribute to TFLog to show warnings for wrong format specifiers or not using a format string
|
||||
- Removed Questions
|
||||
- Removed Feedback View (along with backtrace option)
|
||||
|
||||
Bug Fixes
|
||||
|
||||
- Fixed addrinfo memory leak
|
||||
- Fixed possible `-[TFAirTrafficController getNumberOrNilFrom:withKey:]` crash when bad data is received.
|
||||
- CoreTelephony crash work around: this is a workaround of a iOS bug that causes deallocated instances of `CTTelephonyNetworkInfo` to receive notifications which causes crashes. Core Telephony is used to retrieve the device's mobile carrier.
|
||||
- Fix bug with crash reporting in iOS 7
|
||||
|
||||
|
||||
## 1.2.4 - February 19, 2013
|
||||
|
||||
- Fixed bug that caused crash reports to sometimes not send immediately (they would be resent later)
|
||||
@@ -7,7 +41,7 @@
|
||||
- Fixed typos in readme
|
||||
- Fixed bug where logs not sent on crash
|
||||
- Fixed bug where empty crash files were created (but not sent)
|
||||
- Cache cache path
|
||||
- Cache path to TF's directory so it does not need to be regenerated every time
|
||||
- Use consts for `setOptions:`
|
||||
- Updated `setDeviceIdentifier:` comments to make them clearer
|
||||
- Remove potentially conflicting function name `UIColorFromRGB`
|
||||
|
||||
2
External/UbiquityStoreManager
vendored
@@ -2,8 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>BC894D39-401A-4FED-B9FA-C68C587BC4B4</string>
|
||||
<string>CE2CFE46-2421-46AB-8B34-0FD2B03C1222</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>MasterPassword</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
@@ -18,8 +20,8 @@
|
||||
<string>ssh://github.com/Lyndir/Pearl.git</string>
|
||||
<key>AE3786C7-912B-4651-A73F-2E1DACBFB604</key>
|
||||
<string>git://github.com/lhunath/uicolor-utilities.git</string>
|
||||
<key>DD839E40-2AC0-4574-AE73-CECD1150EBAF</key>
|
||||
<string>ssh://github.com/lhunath/RHStatusItemView.git</string>
|
||||
<key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key>
|
||||
<string>git://github.com/lhunath/RHStatusItemView.git</string>
|
||||
<key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key>
|
||||
<string>git://github.com/jonmarimba/jrswizzle.git</string>
|
||||
<key>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</key>
|
||||
@@ -39,7 +41,7 @@
|
||||
<string>../External/Pearl</string>
|
||||
<key>AE3786C7-912B-4651-A73F-2E1DACBFB604</key>
|
||||
<string>../External/Pearl/External/uicolor-utilities</string>
|
||||
<key>DD839E40-2AC0-4574-AE73-CECD1150EBAF</key>
|
||||
<key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key>
|
||||
<string>../External/RHStatusItemView</string>
|
||||
<key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key>
|
||||
<string>../External/Pearl/External/jrswizzle</string>
|
||||
@@ -58,9 +60,9 @@
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</string>
|
||||
<string>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>MasterPassword</string>
|
||||
<string>FontReplacer</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
@@ -74,9 +76,17 @@
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>5263993D-5FE8-464F-B66E-B0F7C2DFF410</string>
|
||||
<string>E4C8E206-229C-4DA8-A130-0C544DEC7E07</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>UbiquityStoreManager</string>
|
||||
<string>jrswizzle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>MasterPassword</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
@@ -90,17 +100,17 @@
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>E4C8E206-229C-4DA8-A130-0C544DEC7E07</string>
|
||||
<string>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>jrswizzle</string>
|
||||
<string>RHStatusItemView</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</string>
|
||||
<string>5263993D-5FE8-464F-B66E-B0F7C2DFF410</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>FontReplacer</string>
|
||||
<string>UbiquityStoreManager</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
@@ -110,14 +120,6 @@
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>uicolor-utilities</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>DD839E40-2AC0-4574-AE73-CECD1150EBAF</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>RHStatusItemView</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
//
|
||||
|
||||
#import "MPKey.h"
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
|
||||
#define MPAlgorithmDefaultVersion 1
|
||||
@@ -37,8 +38,19 @@
|
||||
- (NSString *)classNameOfType:(MPElementType)type;
|
||||
- (Class)classOfType:(MPElementType)type;
|
||||
|
||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key;
|
||||
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key;
|
||||
- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;
|
||||
|
||||
- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
||||
- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
||||
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey
|
||||
result:(void (^)(NSString *result))resultBlock;
|
||||
|
||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
||||
- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element
|
||||
usingKey:(MPKey *)elementKey;
|
||||
- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -203,27 +203,6 @@
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key {
|
||||
|
||||
if (!element)
|
||||
return nil;
|
||||
|
||||
if (!(element.type & MPElementTypeClassGenerated)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %@, for: %@", [self nameOfType:element.type], element.name);
|
||||
return nil;
|
||||
}
|
||||
if (!element.name.length) {
|
||||
err(@"Missing name.");
|
||||
return nil;
|
||||
}
|
||||
if (!key.keyData.length) {
|
||||
err(@"Missing key.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self generateContentNamed:element.name ofType:element.type withCounter:element.counter usingKey:key];
|
||||
}
|
||||
|
||||
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
||||
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
@@ -247,14 +226,14 @@
|
||||
const char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
assert([seed length]);
|
||||
NSAssert([seed length], @"Missing seed.");
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]]
|
||||
valueForKey:[self nameOfType:type]];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
||||
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
assert([seed length] >= [cipher length] + 1);
|
||||
NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher.");
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
uint16_t keyByte = htons(seedBytes[c + 1]);
|
||||
@@ -270,4 +249,240 @@
|
||||
return content;
|
||||
}
|
||||
|
||||
- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key {
|
||||
|
||||
return [self decryptContent:element.contentObject usingKey:key];
|
||||
}
|
||||
|
||||
- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
case MPElementTypeGeneratedMedium:
|
||||
case MPElementTypeGeneratedBasic:
|
||||
case MPElementTypeGeneratedShort:
|
||||
case MPElementTypeGeneratedPIN: {
|
||||
NSAssert(NO, @"Cannot save content to element with generated type %d.", element.type);
|
||||
break;
|
||||
}
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||
|
||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
((MPElementStoredEntity *)element).contentObject = encryptedContent;
|
||||
break;
|
||||
}
|
||||
case MPElementTypeStoredDevicePrivate: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||
|
||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
||||
if (!encryptedContent)
|
||||
[PearlKeyChain deleteItemForQuery:elementQuery];
|
||||
else
|
||||
[PearlKeyChain addOrUpdateItemForQuery:elementQuery withAttributes:@{
|
||||
(__bridge id)kSecValueData : encryptedContent,
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
#endif
|
||||
}];
|
||||
((MPElementStoredEntity *)element).contentObject = nil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter( group );
|
||||
__block NSString *result = nil;
|
||||
[self resolveContentForElement:element usingKey:elementKey result:^(NSString *result_) {
|
||||
result = result_;
|
||||
dispatch_group_leave( group );
|
||||
}];
|
||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void (^)(NSString *result))resultBlock {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
case MPElementTypeGeneratedMedium:
|
||||
case MPElementTypeGeneratedBasic:
|
||||
case MPElementTypeGeneratedShort:
|
||||
case MPElementTypeGeneratedPIN: {
|
||||
NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]],
|
||||
@"Element with generated type %d is not an MPElementGeneratedEntity, but a %@.", element.type, [element class]);
|
||||
|
||||
NSString *name = element.name;
|
||||
MPElementType type = element.type;
|
||||
NSUInteger counter = ((MPElementGeneratedEntity *)element).counter;
|
||||
id<MPAlgorithm> algorithm = nil;
|
||||
if (!element.name.length)
|
||||
err(@"Missing name.");
|
||||
else if (!elementKey.keyData.length)
|
||||
err(@"Missing key.");
|
||||
else
|
||||
algorithm = element.algorithm;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [algorithm generateContentNamed:name ofType:type withCounter:counter usingKey:elementKey];
|
||||
resultBlock( result );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||
|
||||
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
||||
resultBlock( result );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
case MPElementTypeStoredDevicePrivate: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||
|
||||
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
||||
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
||||
resultBlock( result );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
case MPElementTypeGeneratedMedium:
|
||||
case MPElementTypeGeneratedBasic:
|
||||
case MPElementTypeGeneratedShort:
|
||||
case MPElementTypeGeneratedPIN:
|
||||
break;
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||
if ([importKey.keyID isEqualToData:elementKey.keyID])
|
||||
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
|
||||
|
||||
else {
|
||||
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
|
||||
[self importClearTextContent:clearContent intoElement:element usingKey:elementKey];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
case MPElementTypeGeneratedMedium:
|
||||
case MPElementTypeGeneratedBasic:
|
||||
case MPElementTypeGeneratedShort:
|
||||
case MPElementTypeGeneratedPIN:
|
||||
break;
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
[self saveContent:clearContent toElement:element usingKey:elementKey];
|
||||
break;
|
||||
}
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
if (!(element.type & MPElementFeatureExportContent))
|
||||
return nil;
|
||||
|
||||
NSString *result = nil;
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
case MPElementTypeGeneratedMedium:
|
||||
case MPElementTypeGeneratedBasic:
|
||||
case MPElementTypeGeneratedShort:
|
||||
case MPElementTypeGeneratedPIN: {
|
||||
result = nil;
|
||||
break;
|
||||
}
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
|
||||
break;
|
||||
}
|
||||
|
||||
case MPElementTypeStoredDevicePrivate: {
|
||||
result = nil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)migrateExplicitly:(BOOL)explicit {
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:@{
|
||||
(__bridge id)kSecAttrService : @"DevicePrivate",
|
||||
(__bridge id)kSecAttrAccount : name
|
||||
}
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
|
||||
|
||||
NSData *decryptedContent = nil;
|
||||
if ([encryptedContent length])
|
||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
if (!decryptedContent)
|
||||
return nil;
|
||||
|
||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -68,13 +68,13 @@
|
||||
const unsigned char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
assert([seed length]);
|
||||
NSAssert([seed length], @"Missing seed.");
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] valueForKey:[self nameOfType:type]];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]];
|
||||
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
assert([seed length] >= [cipher length] + 1);
|
||||
NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher.");
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
uint16_t keyByte = seedBytes[c + 1];
|
||||
|
||||
@@ -74,7 +74,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password {
|
||||
|
||||
if (password)
|
||||
NSAssert(![NSThread isMainThread], @"Computing key must not happen from the main thread.");
|
||||
NSAssert(![NSThread isMainThread], @"Computing key must not happen from the main thread.");
|
||||
|
||||
MPKey *tryKey = nil;
|
||||
|
||||
@@ -139,7 +139,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
[Crashlytics setObjectValue:user.userID forKey:@"username"];
|
||||
[Crashlytics setUserName:user.userID];
|
||||
#endif
|
||||
#if TARGET_OS_IPHONE
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] setCustomerName:user.userID];
|
||||
#endif
|
||||
}
|
||||
@@ -171,8 +171,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
for (MPElementEntity *element in user.elements) {
|
||||
if (element.type & MPElementTypeClassStored) {
|
||||
id content = nil;
|
||||
while (!(content = [element contentUsingKey:recoverKey])) {
|
||||
NSString *content;
|
||||
while (!(content = [element.algorithm storedContentForElement:(MPElementStoredEntity *)element usingKey:recoverKey])) {
|
||||
// Failed to decrypt element with the current recoveryKey. Ask user for a new one to use.
|
||||
__block NSString *masterPassword = nil;
|
||||
|
||||
@@ -209,7 +209,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
break;
|
||||
|
||||
if (![recoverKey isEqualToKey:newKey])
|
||||
[element setContent:content usingKey:newKey];
|
||||
[element.algorithm saveContent:content toElement:element usingKey:newKey];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
@implementation MPAppDelegate_Shared {
|
||||
NSManagedObjectID *_activeUserOID;
|
||||
@@ -36,8 +37,10 @@
|
||||
|
||||
NSError *error;
|
||||
MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:_activeUserOID error:&error];
|
||||
if (!activeUser)
|
||||
err(@"Failed to retrieve active user: %@", error);
|
||||
if (!activeUser) {
|
||||
[self signOutAnimated:YES];
|
||||
err(@"Failed to retrieve active user: %@", error);
|
||||
}
|
||||
|
||||
return activeUser;
|
||||
}
|
||||
|
||||
@@ -105,14 +105,15 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication]
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[[self mainManagedObjectContext] saveToStore];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:[UIApplication sharedApplication]
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[[self mainManagedObjectContext] saveToStore];
|
||||
}];
|
||||
^(NSNotification *note) {
|
||||
[[self mainManagedObjectContext] saveToStore];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationWillResignActiveNotification object:[UIApplication sharedApplication]
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[[self mainManagedObjectContext] saveToStore];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
@@ -141,7 +142,10 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
|
||||
inf(@"Local store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPMigrationLevelLocalStoreCurrent);
|
||||
if (migrationLevel <= MPMigrationLevelLocalStoreV1)
|
||||
[self migrateV1LocalStore];
|
||||
if (![self migrateV1LocalStore]) {
|
||||
inf(@"Failed to migrate old V1 to new local store.");
|
||||
return;
|
||||
}
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:MPMigrationLevelLocalStoreCurrent forKey:MPMigrationLevelLocalStoreKey];
|
||||
inf(@"Successfully migrated old to new local store.");
|
||||
@@ -155,15 +159,24 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
return;
|
||||
|
||||
inf(@"Cloud store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPMigrationLevelCloudStoreCurrent);
|
||||
if (migrationLevel <= MPMigrationLevelCloudStoreV1)
|
||||
[self migrateV1CloudStore];
|
||||
else if (migrationLevel <= MPMigrationLevelCloudStoreV2)
|
||||
[self migrateV2CloudStore];
|
||||
if (migrationLevel <= MPMigrationLevelCloudStoreV1) {
|
||||
if (![self migrateV1CloudStore]) {
|
||||
inf(@"Failed to migrate old V1 to new cloud store.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (migrationLevel <= MPMigrationLevelCloudStoreV2) {
|
||||
if (![self migrateV2CloudStore]) {
|
||||
inf(@"Failed to migrate old V2 to new cloud store.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:MPMigrationLevelCloudStoreCurrent forKey:MPMigrationLevelCloudStoreKey];
|
||||
inf(@"Successfully migrated old to new cloud store.");
|
||||
}
|
||||
|
||||
- (void)migrateV1CloudStore {
|
||||
- (BOOL)migrateV1CloudStore {
|
||||
|
||||
// Migrate cloud enabled preference.
|
||||
NSNumber *oldCloudEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:@"iCloudEnabledKey"];
|
||||
@@ -174,7 +187,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"];
|
||||
if (!uuid) {
|
||||
inf(@"No V1 cloud store to migrate.");
|
||||
return;
|
||||
return YES;
|
||||
}
|
||||
|
||||
inf(@"Migrating V1 cloud store: %@ -> %@", uuid, [self.storeManager valueForKey:@"storeUUID"]);
|
||||
@@ -186,16 +199,16 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES]
|
||||
URLByAppendingPathComponent:uuid isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
||||
|
||||
[self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL contentName:uuid];
|
||||
return [self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL contentName:uuid];
|
||||
}
|
||||
|
||||
- (void)migrateV2CloudStore {
|
||||
- (BOOL)migrateV2CloudStore {
|
||||
|
||||
// Migrate cloud store.
|
||||
NSString *uuid = [[NSUbiquitousKeyValueStore defaultStore] stringForKey:@"USMStoreUUIDKey"];
|
||||
if (!uuid) {
|
||||
inf(@"No V2 cloud store to migrate.");
|
||||
return;
|
||||
return YES;
|
||||
}
|
||||
|
||||
inf(@"Migrating V2 cloud store: %@ -> %@", uuid, [self.storeManager valueForKey:@"storeUUID"]);
|
||||
@@ -207,10 +220,10 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
URLByAppendingPathComponent:@"CloudStore.nosync" isDirectory:YES]
|
||||
URLByAppendingPathComponent:uuid isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
||||
|
||||
[self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL contentName:uuid];
|
||||
return [self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL contentName:uuid];
|
||||
}
|
||||
|
||||
- (void)migrateV1LocalStore {
|
||||
- (BOOL)migrateV1LocalStore {
|
||||
|
||||
NSURL *applicationFilesDirectory = [[[NSFileManager defaultManager]
|
||||
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
@@ -218,92 +231,48 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NO]) {
|
||||
inf(@"No V1 local store to migrate.");
|
||||
return;
|
||||
return YES;
|
||||
}
|
||||
|
||||
inf(@"Migrating V1 local store");
|
||||
[self migrateFromLocalStore:oldLocalStoreURL];
|
||||
return [self migrateFromLocalStore:oldLocalStoreURL];
|
||||
}
|
||||
|
||||
- (void)migrateFromLocalStore:(NSURL *)oldLocalStoreURL {
|
||||
- (BOOL)migrateFromLocalStore:(NSURL *)oldLocalStoreURL {
|
||||
|
||||
NSURL *newLocalStoreURL = [self.storeManager URLForLocalStore];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) {
|
||||
wrn(@"Can't migrate local store: A new local store already exists.");
|
||||
return;
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSDictionary *oldLocalStoreOptions = @{
|
||||
STORE_OPTIONS
|
||||
NSReadOnlyPersistentStoreOption : @YES,
|
||||
NSInferMappingModelAutomaticallyOption : @YES
|
||||
};
|
||||
NSDictionary *newLocalStoreOptions = @{
|
||||
STORE_OPTIONS
|
||||
NSMigratePersistentStoresAutomaticallyOption : @YES,
|
||||
NSInferMappingModelAutomaticallyOption : @YES
|
||||
};
|
||||
|
||||
// Create the directory to hold the new local store.
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:[self.storeManager URLForLocalStoreDirectory].path
|
||||
withIntermediateDirectories:YES attributes:nil error:&error])
|
||||
err(@"While creating directory for new local store: %@", error);
|
||||
|
||||
if (![self.storeManager copyMigrateStore:oldLocalStoreURL withOptions:oldLocalStoreOptions
|
||||
toStore:newLocalStoreURL withOptions:newLocalStoreOptions
|
||||
error:nil cause:nil context:nil])
|
||||
return;
|
||||
if (![self.storeManager migrateStore:oldLocalStoreURL withOptions:nil
|
||||
toStore:newLocalStoreURL withOptions:nil
|
||||
strategy:0 error:nil cause:nil context:nil]) {
|
||||
self.storeManager.localStoreURL = oldLocalStoreURL;
|
||||
return NO;
|
||||
}
|
||||
|
||||
inf(@"Successfully migrated to new local store.");
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)migrateFromCloudStore:(NSURL *)oldCloudStoreURL cloudContent:(NSURL *)oldCloudContentURL contentName:(NSString *)contentName {
|
||||
- (BOOL)migrateFromCloudStore:(NSURL *)oldCloudStoreURL cloudContent:(NSURL *)oldCloudContentURL contentName:(NSString *)contentName {
|
||||
|
||||
if (![self.storeManager cloudSafeForSeeding]) {
|
||||
inf(@"Can't migrate cloud store: A new cloud store already exists.");
|
||||
return;
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSURL *newCloudStoreURL = [self.storeManager URLForCloudStore];
|
||||
NSURL *newCloudContentURL = [self.storeManager URLForCloudContent];
|
||||
|
||||
NSError *error = nil;
|
||||
NSDictionary *oldCloudStoreOptions = @{
|
||||
STORE_OPTIONS
|
||||
NSPersistentStoreUbiquitousContentNameKey : contentName,
|
||||
NSPersistentStoreUbiquitousContentURLKey : oldCloudContentURL,
|
||||
NSInferMappingModelAutomaticallyOption : @YES
|
||||
};
|
||||
NSDictionary *newCloudStoreOptions = @{
|
||||
STORE_OPTIONS
|
||||
NSPersistentStoreUbiquitousContentNameKey : [self.storeManager valueForKey:@"contentName"],
|
||||
NSPersistentStoreUbiquitousContentURLKey : newCloudContentURL,
|
||||
NSMigratePersistentStoresAutomaticallyOption : @YES,
|
||||
NSInferMappingModelAutomaticallyOption : @YES
|
||||
};
|
||||
|
||||
// Create the directory to hold the new cloud store.
|
||||
// This is only necessary if we want to try to rebuild the old store. See comment above about how that failed.
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:[oldCloudStoreURL URLByDeletingLastPathComponent].path
|
||||
withIntermediateDirectories:YES attributes:nil error:&error])
|
||||
err(@"While creating directory for old cloud store: %@", error);
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:oldCloudContentURL.path
|
||||
withIntermediateDirectories:YES attributes:nil error:&error])
|
||||
err(@"While creating directory for old cloud content: %@", error);
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:[self.storeManager URLForCloudStoreDirectory].path
|
||||
withIntermediateDirectories:YES attributes:nil error:&error])
|
||||
err(@"While creating directory for new cloud store: %@", error);
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:[self.storeManager URLForCloudContent].path
|
||||
withIntermediateDirectories:YES attributes:nil error:&error])
|
||||
err(@"While creating directory for new cloud content: %@", error);
|
||||
|
||||
if (![self.storeManager copyMigrateStore:oldCloudStoreURL withOptions:oldCloudStoreOptions
|
||||
toStore:newCloudStoreURL withOptions:newCloudStoreOptions
|
||||
error:nil cause:nil context:nil])
|
||||
return;
|
||||
if (![self.storeManager migrateStore:oldCloudStoreURL withOptions:nil
|
||||
toStore:newCloudStoreURL withOptions:nil
|
||||
strategy:0 error:nil cause:nil context:nil])
|
||||
return NO;
|
||||
|
||||
inf(@"Successfully migrated to new cloud store.");
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - UbiquityStoreManagerDelegate
|
||||
@@ -371,11 +340,11 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause
|
||||
context:(id)context {
|
||||
|
||||
err(@"[StoreManager] ERROR: cause=%d, context=%@, error=%@", cause, context, error);
|
||||
err(@"[StoreManager] ERROR: cause=%@, context=%@, error=%@", NSStringFromUSMCause( cause ), context, error);
|
||||
MPCheckpoint( MPCheckpointMPErrorUbiquity, @{
|
||||
@"cause" : @(cause),
|
||||
@"error.domain" : error.domain,
|
||||
@"error.code" : @(error.code)
|
||||
@"error.code" : @(error.code),
|
||||
@"error.domain" : NilToNSNull(error.domain)
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -390,7 +359,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [self activeUserInContext:context];
|
||||
assert(activeUser);
|
||||
NSAssert(activeUser, @"Missing user.");
|
||||
if (!activeUser)
|
||||
return;
|
||||
|
||||
MPElementType type = activeUser.defaultType;
|
||||
if (!type)
|
||||
@@ -413,7 +384,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
|
||||
NSManagedObjectID *elementOID = [element objectID];
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
completion( (MPElementEntity *)[[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady] objectRegisteredForID:elementOID] );
|
||||
completion(
|
||||
(MPElementEntity *)[[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady] objectRegisteredForID:elementOID] );
|
||||
} );
|
||||
}];
|
||||
}
|
||||
@@ -656,14 +628,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
element.version = version;
|
||||
if ([exportContent length]) {
|
||||
if (clearText)
|
||||
[element importClearTextContent:exportContent usingKey:userKey];
|
||||
[element.algorithm importClearTextContent:exportContent intoElement:element usingKey:userKey];
|
||||
else {
|
||||
if (!importKey)
|
||||
importKey = [importAlgorithm keyForPassword:importPassword( user.name ) ofUserNamed:user.name];
|
||||
if (![importKey.keyID isEqualToData:importKeyID])
|
||||
return MPImportResultInvalidPassword;
|
||||
|
||||
[element importProtectedContent:exportContent protectedByKey:importKey usingKey:userKey];
|
||||
[element.algorithm importProtectedContent:exportContent protectedByKey:importKey intoElement:element usingKey:userKey];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,9 +690,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
// Determine the content to export.
|
||||
if (!(type & MPElementFeatureDevicePrivate)) {
|
||||
if (showPasswords)
|
||||
content = element.content;
|
||||
content = [element.algorithm resolveContentForElement:element usingKey:self.key];
|
||||
else if (type & MPElementFeatureExportContent)
|
||||
content = element.exportContent;
|
||||
content = [element.algorithm exportContentForElement:element usingKey:self.key];
|
||||
}
|
||||
|
||||
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
@interface MPElementEntity : NSManagedObject
|
||||
|
||||
@property(nonatomic, retain) id content;
|
||||
@property(nonatomic, retain) NSDate *lastUsed;
|
||||
@property(nonatomic, retain) NSString *loginName;
|
||||
@property(nonatomic, retain) NSString *name;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
@implementation MPElementEntity
|
||||
|
||||
@dynamic content;
|
||||
@dynamic lastUsed;
|
||||
@dynamic loginName;
|
||||
@dynamic name;
|
||||
|
||||
@@ -33,13 +33,7 @@
|
||||
@property(assign) BOOL requiresExplicitMigration;
|
||||
@property(readonly) id<MPAlgorithm> algorithm;
|
||||
|
||||
- (id)contentUsingKey:(MPKey *)key;
|
||||
- (void)setContent:(id)content usingKey:(MPKey *)key;
|
||||
|
||||
- (NSUInteger)use;
|
||||
- (NSString *)exportContent;
|
||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key2;
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key;
|
||||
- (BOOL)migrateExplicitly:(BOOL)explicit;
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,14 +13,21 @@
|
||||
|
||||
- (BOOL)saveToStore {
|
||||
|
||||
__block BOOL success = NO;
|
||||
[self performBlockAndWait:^{
|
||||
NSError *error = nil;
|
||||
if (!(success = [self save:&error]))
|
||||
err(@"While saving: %@", error);
|
||||
}];
|
||||
__block BOOL success = YES;
|
||||
if ([self hasChanges])
|
||||
[self performBlockAndWait:^{
|
||||
@try {
|
||||
NSError *error = nil;
|
||||
if (!(success = [self save:&error]))
|
||||
err(@"While saving: %@", error);
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
success = NO;
|
||||
err(@"While saving: %@", exception);
|
||||
}
|
||||
}];
|
||||
|
||||
return !self.parentContext || [self.parentContext saveToStore];
|
||||
return success && (!self.parentContext || [self.parentContext saveToStore]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -111,47 +118,6 @@
|
||||
return ++self.uses;
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||
if (!key)
|
||||
return nil;
|
||||
|
||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
||||
return [self contentUsingKey:key];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
|
||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
||||
[self setContent:content usingKey:key];
|
||||
}
|
||||
|
||||
- (id)contentUsingKey:(MPKey *)key {
|
||||
|
||||
Throw(@"Content retrieval implementation missing for: %@", [self class]);
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content usingKey:(MPKey *)key {
|
||||
|
||||
Throw(@"Content assignment implementation missing for: %@", [self class]);
|
||||
}
|
||||
|
||||
- (NSString *)exportContent {
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key {
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
|
||||
return PearlString( @"%@:%@", [self class], [self name] );
|
||||
@@ -191,107 +157,10 @@
|
||||
self.counter_ = @(aCounter);
|
||||
}
|
||||
|
||||
- (id)contentUsingKey:(MPKey *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassGenerated);
|
||||
|
||||
if (![self.name length])
|
||||
return nil;
|
||||
if (!key)
|
||||
return nil;
|
||||
|
||||
return [self.algorithm generateContentForElement:self usingKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPElementStoredEntity(MP)
|
||||
|
||||
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:@{
|
||||
(__bridge id)kSecAttrService : @"DevicePrivate",
|
||||
(__bridge id)kSecAttrAccount : name
|
||||
}
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (id)contentUsingKey:(MPKey *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
|
||||
if (!key)
|
||||
return nil;
|
||||
|
||||
NSData *encryptedContent;
|
||||
if (self.type & MPElementFeatureDevicePrivate)
|
||||
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
|
||||
else
|
||||
encryptedContent = self.contentObject;
|
||||
|
||||
NSData *decryptedContent = nil;
|
||||
if ([encryptedContent length])
|
||||
decryptedContent = [self decryptContent:encryptedContent usingKey:key];
|
||||
|
||||
if (!decryptedContent)
|
||||
return nil;
|
||||
|
||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (NSData *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
|
||||
|
||||
return [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content usingKey:(MPKey *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent = [[[content description] dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
|
||||
if (self.type & MPElementFeatureDevicePrivate) {
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
encryptedContent, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
(__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
self.contentObject = nil;
|
||||
}
|
||||
else
|
||||
self.contentObject = encryptedContent;
|
||||
}
|
||||
|
||||
- (NSString *)exportContent {
|
||||
|
||||
return [self.contentObject encodeBase64];
|
||||
}
|
||||
|
||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key {
|
||||
|
||||
if ([contentProtectionKey.keyID isEqualToData:key.keyID])
|
||||
self.contentObject = [protectedContent decodeBase64];
|
||||
|
||||
else {
|
||||
NSString *clearContent = [[NSString alloc] initWithData:[self decryptContent:[protectedContent decodeBase64]
|
||||
usingKey:contentProtectionKey]
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
[self importClearTextContent:clearContent usingKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
|
||||
|
||||
[self setContent:clearContent usingKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPUserEntity(MP)
|
||||
|
||||
@@ -51,10 +51,9 @@
|
||||
options:nil];
|
||||
self.pageViewController.dataSource = self;
|
||||
self.pageViewController.delegate = self;
|
||||
self.pageViewController.view.frame = CGRectFromOriginWithSize( CGPointZero, self.pagePositionView.bounds.size );
|
||||
[self addChildViewController:self.pageViewController];
|
||||
[self.view addSubview:self.pageViewController.view];
|
||||
self.pageViewController.view.frame = self.pagePositionView.frame;
|
||||
[self.pagePositionView removeFromSuperview];
|
||||
[self.pagePositionView addSubview:self.pageViewController.view];
|
||||
[self.pageViewController didMoveToParentViewController:self];
|
||||
|
||||
[super viewDidLoad];
|
||||
@@ -73,8 +72,10 @@
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Apps"];
|
||||
|
||||
#endif
|
||||
|
||||
[self.pageViewController setViewControllers:@[ [self.pageVCs objectAtIndex:0] ] direction:UIPageViewControllerNavigationDirectionForward
|
||||
animated:YES completion:nil];
|
||||
|
||||
|
||||
@@ -99,11 +99,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||
NSMutableDictionary *elementChanges = [NSMutableDictionary dictionaryWithCapacity:[elements count]];
|
||||
for (MPElementEntity *element in elements) {
|
||||
id oldContent = [element content];
|
||||
id oldContent = [element.algorithm resolveContentForElement:element usingKey:key];
|
||||
[element migrateExplicitly:YES];
|
||||
id newContent = [element content];
|
||||
id newContent = [element.algorithm resolveContentForElement:element usingKey:key];
|
||||
|
||||
if (!(element.type & MPElementFeatureDevicePrivate) && (!oldContent || ![oldContent isEqual:newContent]))
|
||||
[elementChanges setObject:@{
|
||||
|
||||
@@ -271,7 +271,7 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[context saveToStore];
|
||||
|
||||
MPCheckpoint( MPCheckpointDeleteElement, @{
|
||||
@"type" : element.typeName,
|
||||
@"type" : NilToNSNull(element.typeName),
|
||||
@"version" : @(element.version)
|
||||
} );
|
||||
}];
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
return UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
@@ -44,6 +49,10 @@
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[self.navigationController setNavigationBarHidden:YES animated:animated];
|
||||
if (![super respondsToSelector:@selector(prefersStatusBarHidden)])
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
|
||||
|
||||
inf(@"Guide will appear.");
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
@@ -67,7 +76,9 @@
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Guide"];
|
||||
#endif
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -63,9 +65,9 @@
|
||||
return;
|
||||
|
||||
switchCloudStoreProgress = [PearlAlert showActivityWithTitle:@"Enumerating Stores"];
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
|
||||
[self switchCloudStore];
|
||||
});
|
||||
} );
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
@@ -74,40 +76,77 @@
|
||||
|
||||
- (void)switchCloudStore {
|
||||
|
||||
NSError *error = nil;
|
||||
NSURL *cloudContentDirectory = [[MPiOSAppDelegate get].storeManager URLForCloudContentDirectory];
|
||||
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:cloudContentDirectory includingPropertiesForKeys:nil
|
||||
options:NSDirectoryEnumerationSkipsHiddenFiles error:&error];
|
||||
if (!contents)
|
||||
err(@"While enumerating cloud contents: %@", error);
|
||||
NSDictionary *cloudStores = [[MPiOSAppDelegate get].storeManager enumerateCloudStores];
|
||||
if (!cloudStores)
|
||||
wrn(@"Failed enumerating cloud stores.");
|
||||
|
||||
NSMutableArray *contentNames = [NSMutableArray arrayWithCapacity:[contents count]];
|
||||
BOOL directory = NO;
|
||||
for (NSURL *content in contents)
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:content.path isDirectory:&directory] && directory)
|
||||
[contentNames addObject:[content lastPathComponent]];
|
||||
NSString *currentStoreUUID = nil;
|
||||
NSMutableDictionary *stores = [NSMutableDictionary dictionary];
|
||||
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
|
||||
NSPersistentStoreCoordinator *storePSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
|
||||
NSFetchRequest *usersFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||
NSFetchRequest *sitesFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
||||
for (NSURL *cloudStoreURL in cloudStores) {
|
||||
NSString *storeUUID = [[cloudStoreURL URLByDeletingPathExtension] lastPathComponent];
|
||||
for (NSDictionary *cloudStoreOptions in [cloudStores objectForKey:cloudStoreURL]) {
|
||||
NSError *error = nil;
|
||||
NSPersistentStore *store = nil;
|
||||
NSUInteger firstDash = [storeUUID rangeOfString:@"-" options:0].location;
|
||||
NSString *storeDescription = PearlString( @"%@ v%@",
|
||||
firstDash == NSNotFound? storeUUID: [storeUUID substringToIndex:firstDash],
|
||||
cloudStoreOptions[USMCloudVersionKey] );
|
||||
if ([cloudStoreOptions[USMCloudCurrentKey] boolValue])
|
||||
currentStoreUUID = storeUUID;
|
||||
@try {
|
||||
if (!(store = [storePSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil
|
||||
URL:cloudStoreURL options:cloudStoreOptions error:&error])) {
|
||||
wrn(@"Couldn't describe store %@. While opening: %@", storeDescription, error);
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *storeUUID = [[MPiOSAppDelegate get].storeManager valueForKey:@"storeUUID_ThreadSafe"];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSUInteger userCount, siteCount;
|
||||
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||
moc.persistentStoreCoordinator = storePSC;
|
||||
if ((userCount = [moc countForFetchRequest:usersFetchRequest error:&error]) == NSNotFound) {
|
||||
wrn(@"Couldn't describe store %@. While determining userCount: %@", storeDescription, error);
|
||||
continue;
|
||||
}
|
||||
if ((siteCount = [moc countForFetchRequest:sitesFetchRequest error:&error]) == NSNotFound) {
|
||||
wrn(@"Couldn't describe store %@. While determining siteCount: %@", storeDescription, error);
|
||||
continue;
|
||||
}
|
||||
|
||||
storeDescription = PearlString( @"%@: %dU, %dS", storeDescription, userCount, siteCount );
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
wrn(@"Couldn't describe store %@: %@", storeDescription, exception);
|
||||
}
|
||||
@finally {
|
||||
if (store && ![storePSC removePersistentStore:store error:&error]) {
|
||||
wrn(@"Couldn't remove store %@: %@", storeDescription, error);
|
||||
storePSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
|
||||
}
|
||||
|
||||
[stores setObject:cloudStoreOptions forKey:storeDescription];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PearlArrayTVC *vc = [[PearlArrayTVC alloc] initWithStyle:UITableViewStylePlain];
|
||||
NSUInteger firstDash = [currentStoreUUID rangeOfString:@"-" options:0].location;
|
||||
vc.title = PearlString( @"Active: %@", firstDash == NSNotFound? currentStoreUUID: [currentStoreUUID substringToIndex:firstDash] );
|
||||
[stores enumerateKeysAndObjectsUsingBlock:^(id storeDescription, id cloudStoreOptions, BOOL *stop) {
|
||||
[vc addRowWithName:storeDescription style:PearlArrayTVCRowStyleLink toggled:[cloudStoreOptions[USMCloudCurrentKey] boolValue]
|
||||
toSection:@"Cloud Stores" activationBlock:^BOOL(BOOL wasToggled) {
|
||||
[[MPiOSAppDelegate get].storeManager switchToCloudStoreWithOptions:cloudStoreOptions];
|
||||
[self.navigationController popToRootViewControllerAnimated:YES];
|
||||
return YES;
|
||||
}];
|
||||
}];
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[switchCloudStoreProgress cancelAlertAnimated:YES];
|
||||
|
||||
[PearlSheet showSheetWithTitle:storeUUID
|
||||
viewStyle:UIActionSheetStyleAutomatic
|
||||
initSheet:^(UIActionSheet *sheet) {
|
||||
for (NSString *contentName in contentNames) {
|
||||
[sheet addButtonWithTitle:contentName];
|
||||
}
|
||||
}
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == sheet.cancelButtonIndex)
|
||||
return;
|
||||
|
||||
[[MPiOSAppDelegate get].storeManager setValue:[contentNames objectAtIndex:(unsigned)buttonIndex] forKey:@"storeUUID"];
|
||||
[[MPiOSAppDelegate get].storeManager reloadStore];
|
||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:nil];
|
||||
});
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
} );
|
||||
}
|
||||
|
||||
- (IBAction)toggleLevelControl:(UISegmentedControl *)sender {
|
||||
|
||||
@@ -85,7 +85,8 @@
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
MPElementEntity *activeElement = [self activeElementForMainThread];
|
||||
if (activeElement.type & MPElementTypeClassStored && ![[activeElement.content description] length])
|
||||
if (activeElement.type & MPElementTypeClassStored &&
|
||||
![[activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key] length])
|
||||
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
|
||||
if (activeElement.requiresExplicitMigration)
|
||||
[self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil];
|
||||
@@ -121,6 +122,10 @@
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[self.navigationController setNavigationBarHidden:NO animated:animated];
|
||||
if (![super respondsToSelector:@selector(prefersStatusBarHidden)])
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
|
||||
|
||||
MPElementEntity *activeElement = [self activeElementForMainThread];
|
||||
if (activeElement.user != [[MPiOSAppDelegate get] activeUserForMainThread])
|
||||
_activeElementOID = nil;
|
||||
@@ -180,7 +185,9 @@
|
||||
} );
|
||||
}];
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Main"];
|
||||
#endif
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
@@ -240,13 +247,11 @@
|
||||
self.contentField.enabled = NO;
|
||||
self.contentField.text = @"";
|
||||
if (activeElement.name && ![activeElement isDeleted])
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
|
||||
NSString *description = [activeElement.content description];
|
||||
|
||||
[activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
self.contentField.text = description;
|
||||
self.contentField.text = result;
|
||||
} );
|
||||
} );
|
||||
}];
|
||||
|
||||
self.loginNameField.enabled = NO;
|
||||
self.loginNameField.text = activeElement.loginName;
|
||||
@@ -319,7 +324,7 @@
|
||||
- (void)setHelpChapter:(NSString *)chapter {
|
||||
|
||||
MPCheckpoint( MPCheckpointHelpChapter, @{
|
||||
@"chapter" : chapter
|
||||
@"chapter" : NilToNSNull(chapter)
|
||||
} );
|
||||
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
@@ -468,21 +473,21 @@
|
||||
- (IBAction)copyContent {
|
||||
|
||||
MPElementEntity *activeElement = [self activeElementForMainThread];
|
||||
id content = activeElement.content;
|
||||
if (!content)
|
||||
// Nothing to copy.
|
||||
return;
|
||||
|
||||
inf(@"Copying password for: %@", activeElement.name);
|
||||
[UIPasteboard generalPasteboard].string = [content description];
|
||||
|
||||
[self showContentTip:@"Copied!" withIcon:nil];
|
||||
|
||||
MPCheckpoint( MPCheckpointCopyToPasteboard, @{
|
||||
@"type" : activeElement.typeName,
|
||||
@"type" : NilToNSNull(activeElement.typeName),
|
||||
@"version" : @(activeElement.version),
|
||||
@"emergency" : @NO
|
||||
} );
|
||||
|
||||
[activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||
if (!result)
|
||||
// Nothing to copy.
|
||||
return;
|
||||
|
||||
[UIPasteboard generalPasteboard].string = result;
|
||||
[self showContentTip:@"Copied!" withIcon:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)copyLoginName:(UITapGestureRecognizer *)sender {
|
||||
@@ -497,7 +502,7 @@
|
||||
[self showLoginNameTip:@"Copied!"];
|
||||
|
||||
MPCheckpoint( MPCheckpointCopyLoginNameToPasteboard, @{
|
||||
@"type" : activeElement.typeName,
|
||||
@"type" : NilToNSNull(activeElement.typeName),
|
||||
@"version" : @(activeElement.version)
|
||||
} );
|
||||
}
|
||||
@@ -521,7 +526,7 @@
|
||||
++activeGeneratedElement.counter;
|
||||
|
||||
MPCheckpoint( MPCheckpointIncrementPasswordCounter, @{
|
||||
@"type" : activeGeneratedElement.typeName,
|
||||
@"type" : NilToNSNull(activeGeneratedElement.typeName),
|
||||
@"version" : @(activeGeneratedElement.version),
|
||||
@"counter" : @(activeGeneratedElement.counter)
|
||||
} );
|
||||
@@ -553,7 +558,7 @@
|
||||
((MPElementGeneratedEntity *)activeElement_).counter = 1;
|
||||
|
||||
MPCheckpoint( MPCheckpointResetPasswordCounter, @{
|
||||
@"type" : activeElement_.typeName,
|
||||
@"type" : NilToNSNull(activeElement_.typeName),
|
||||
@"version" : @(activeElement_.version)
|
||||
} );
|
||||
return YES;
|
||||
@@ -574,7 +579,7 @@
|
||||
[self.loginNameField becomeFirstResponder];
|
||||
|
||||
MPCheckpoint( MPCheckpointEditLoginName, @{
|
||||
@"type" : activeElement.typeName,
|
||||
@"type" : NilToNSNull(activeElement.typeName),
|
||||
@"version" : @(activeElement.version)
|
||||
} );
|
||||
}
|
||||
@@ -598,12 +603,13 @@
|
||||
if (!activeElement)
|
||||
return;
|
||||
|
||||
NSString *oldPassword = [activeElement.content description];
|
||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||
NSString *oldPassword = [activeElement.algorithm resolveContentForElement:activeElement usingKey:key];
|
||||
if (!task( activeElement, context ))
|
||||
return;
|
||||
|
||||
|
||||
activeElement = [self activeElementInContext:context];
|
||||
NSString *newPassword = [activeElement.content description];
|
||||
NSString *newPassword = [activeElement.algorithm resolveContentForElement:activeElement usingKey:key];
|
||||
|
||||
// Save.
|
||||
[context saveToStore];
|
||||
@@ -655,7 +661,7 @@
|
||||
[self.contentField becomeFirstResponder];
|
||||
|
||||
MPCheckpoint( MPCheckpointEditPassword, @{
|
||||
@"type" : activeElement.typeName,
|
||||
@"type" : NilToNSNull(activeElement.typeName),
|
||||
@"version" : @(activeElement.version)
|
||||
} );
|
||||
}
|
||||
@@ -680,7 +686,7 @@
|
||||
[activeElement_ migrateExplicitly:YES];
|
||||
|
||||
MPCheckpoint( MPCheckpointExplicitMigration, @{
|
||||
@"type" : activeElement_.typeName,
|
||||
@"type" : NilToNSNull(activeElement_.typeName),
|
||||
@"version" : @(activeElement_.version)
|
||||
} );
|
||||
return YES;
|
||||
@@ -767,11 +773,11 @@
|
||||
}
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
|
||||
@"? FAQ ",
|
||||
@"ⓘ Quick Guide ",
|
||||
@"⚙ Preferences ",
|
||||
@"⚐ Other Apps ",
|
||||
@"✎ Feedback ",
|
||||
@"FAQ",
|
||||
@"Overview",
|
||||
@"User Profile",
|
||||
@"Other Apps",
|
||||
@"Feedback",
|
||||
nil];
|
||||
}
|
||||
|
||||
@@ -831,11 +837,11 @@
|
||||
} );
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
MPCheckpoint( MPCheckpointUseType, @{
|
||||
@"type" : element.typeName,
|
||||
@"version" : @(element.version)
|
||||
} );
|
||||
@"type" : NilToNSNull(element.typeName),
|
||||
@"version" : @(element.version)
|
||||
} );
|
||||
}
|
||||
|
||||
[self.searchDisplayController setActive:NO animated:YES];
|
||||
@@ -859,17 +865,18 @@
|
||||
if (textField == self.contentField) {
|
||||
self.contentField.enabled = NO;
|
||||
MPElementEntity *activeElement = [self activeElementForMainThread];
|
||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||
if (![activeElement isKindOfClass:[MPElementStoredEntity class]]) {
|
||||
// Not of a type whose content can be edited.
|
||||
err(@"Cannot update element content: Element is not stored: %@", activeElement.name);
|
||||
return;
|
||||
}
|
||||
else if ([((MPElementStoredEntity *)activeElement).content isEqual:self.contentField.text])
|
||||
else if ([[activeElement.algorithm resolveContentForElement:activeElement usingKey:key] isEqual:self.contentField.text])
|
||||
// Content hasn't changed.
|
||||
return;
|
||||
|
||||
[self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement_, NSManagedObjectContext *context) {
|
||||
((MPElementStoredEntity *)activeElement_).content = self.contentField.text;
|
||||
[activeElement_.algorithm saveContent:self.contentField.text toElement:activeElement_ usingKey:key];
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import "MPPreferencesViewController.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
@@ -79,7 +80,9 @@
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Preferences"];
|
||||
#endif
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
@@ -99,7 +102,7 @@
|
||||
|
||||
if (motion == UIEventSubtypeMotionShake) {
|
||||
MPCheckpoint( MPCheckpointLogs, @{
|
||||
@"trace": [MPiOSConfig get].traceMode
|
||||
@"trace" : [MPiOSConfig get].traceMode
|
||||
} );
|
||||
[self performSegueWithIdentifier:@"MP_Logs" sender:self];
|
||||
}
|
||||
|
||||
@@ -24,12 +24,14 @@
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Setup"];
|
||||
#endif
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
if (self.cloudSwitch && [[MPiOSConfig get].iCloudDecided boolValue])
|
||||
self.cloudSwitch.on = [MPiOSAppDelegate get].storeManager.cloudEnabled;
|
||||
self.cloudSwitch.on = [[MPiOSConfig get].iCloudEnabled boolValue];
|
||||
if (self.rememberLoginSwitch)
|
||||
self.rememberLoginSwitch.on = [[MPiOSConfig get].rememberLogin boolValue];
|
||||
}
|
||||
@@ -40,7 +42,7 @@
|
||||
|
||||
if (self.cloudSwitch) {
|
||||
[MPiOSConfig get].iCloudDecided = @YES;
|
||||
[MPiOSAppDelegate get].storeManager.cloudEnabled = self.cloudSwitch.on;
|
||||
[MPiOSConfig get].iCloudEnabled = @(self.cloudSwitch.on);
|
||||
}
|
||||
if (self.rememberLoginSwitch)
|
||||
[MPiOSConfig get].rememberLogin = @(self.rememberLoginSwitch.on);
|
||||
|
||||
@@ -43,7 +43,9 @@
|
||||
}
|
||||
}];
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Type Selection"];
|
||||
#endif
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
@@ -96,7 +98,7 @@
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
assert(self.navigationController.topViewController == self);
|
||||
NSAssert(self.navigationController.topViewController == self, @"Not the currently active navigation item.");
|
||||
|
||||
MPElementType type = [self typeAtIndexPath:indexPath];
|
||||
if (type == (MPElementType)NSNotFound)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
@property(weak, nonatomic) IBOutlet UIView *wordWall;
|
||||
@property(strong, nonatomic) IBOutlet UILongPressGestureRecognizer *targetedUserActionGesture;
|
||||
@property(weak, nonatomic) IBOutlet UIView *uiContainer;
|
||||
@property(weak, nonatomic) IBOutlet UIView *shareContainer;
|
||||
@property(weak, nonatomic) IBOutlet UIWebView *newsView;
|
||||
@property(weak, nonatomic) IBOutlet UIView *emergencyGeneratorContainer;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *emergencyName;
|
||||
@@ -37,15 +38,14 @@
|
||||
@property(weak, nonatomic) IBOutlet UIButton *emergencyPassword;
|
||||
@property(weak, nonatomic) IBOutlet UIView *emergencyContentTipContainer;
|
||||
|
||||
@property(nonatomic, strong) UIColor *avatarShadowColor;
|
||||
|
||||
- (IBAction)targetedUserAction:(UILongPressGestureRecognizer *)sender;
|
||||
- (IBAction)facebook:(UIButton *)sender;
|
||||
- (IBAction)twitter:(UIButton *)sender;
|
||||
- (IBAction)google:(UIButton *)sender;
|
||||
- (IBAction)mail:(UIButton *)sender;
|
||||
- (IBAction)add:(UIButton *)sender;
|
||||
- (IBAction)emergencyClose:(UIButton *)sender;
|
||||
- (IBAction)emergencyCopy:(UIButton *)sender;
|
||||
- (IBAction)facebook:(id)sender;
|
||||
- (IBAction)twitter:(id)sender;
|
||||
- (IBAction)google:(id)sender;
|
||||
- (IBAction)mail:(id)sender;
|
||||
- (IBAction)add:(id)sender;
|
||||
- (IBAction)emergencyOpen:(id)sender;
|
||||
- (IBAction)emergencyClose:(id)sender;
|
||||
- (IBAction)emergencyCopy:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,16 +6,14 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <Social/Social.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import "MPUnlockViewController.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
|
||||
@interface MPUnlockViewController()
|
||||
|
||||
@property(strong, nonatomic) NSMutableDictionary *avatarToUserOID;
|
||||
@@ -30,7 +28,6 @@
|
||||
@property(nonatomic, strong) NSArray *marqueeTipTexts;
|
||||
@end
|
||||
|
||||
|
||||
@implementation MPUnlockViewController {
|
||||
NSManagedObjectID *_selectedUserOID;
|
||||
}
|
||||
@@ -182,6 +179,7 @@
|
||||
^(NSNotification *note) {
|
||||
[self emergencyCloseAnimated:NO];
|
||||
self.uiContainer.alpha = 0;
|
||||
self.shareContainer.alpha = 0;
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
@@ -189,9 +187,14 @@
|
||||
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.uiContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.shareContainer.alpha = 1;
|
||||
}];
|
||||
}];
|
||||
|
||||
NSString *newsURL = PearlString( @"http://masterpasswordapp.com/news-dev.html?version=%@",
|
||||
NSString *newsURL = PearlString( @"http://masterpasswordapp.com/news.html?version=%@",
|
||||
[[PearlInfoPlist get] CFBundleVersion] );
|
||||
[self.newsView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:newsURL]]];
|
||||
}];
|
||||
@@ -205,7 +208,8 @@
|
||||
|
||||
inf(@"Lock screen will appear");
|
||||
[self.navigationController setNavigationBarHidden:YES animated:animated];
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
|
||||
if (![super respondsToSelector:@selector(prefersStatusBarHidden)])
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
|
||||
|
||||
[[MPiOSAppDelegate get] signOutAnimated:NO];
|
||||
|
||||
@@ -213,6 +217,7 @@
|
||||
[self updateUsers];
|
||||
|
||||
self.uiContainer.alpha = 0;
|
||||
self.shareContainer.alpha = 0;
|
||||
self.spinner.alpha = 0;
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
@@ -227,14 +232,21 @@
|
||||
else
|
||||
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
|
||||
|
||||
[UIView animateWithDuration:0.5 animations:^{
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.uiContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.shareContainer.alpha = 1;
|
||||
}];
|
||||
}];
|
||||
|
||||
[self.marqueeTipTimer invalidate];
|
||||
self.marqueeTipTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(marqueeTip) userInfo:nil repeats:YES];
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Unlock"];
|
||||
#endif
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
@@ -243,17 +255,27 @@
|
||||
|
||||
inf(@"Lock screen will disappear");
|
||||
[self emergencyCloseAnimated:animated];
|
||||
|
||||
[self.marqueeTipTimer invalidate];
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self.navigationController setNavigationBarHidden:NO animated:animated];
|
||||
}];
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"MP_Settings"])
|
||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
|
||||
|
||||
return UIStatusBarAnimationSlide;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
|
||||
return YES;
|
||||
@@ -261,29 +283,8 @@
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
|
||||
if (motion == UIEventSubtypeMotionShake) {
|
||||
MPCheckpoint( MPCheckpointEmergencyGenerator, nil );
|
||||
[[self.view findFirstResponderInHierarchy] resignFirstResponder];
|
||||
|
||||
self.emergencyGeneratorContainer.alpha = 0;
|
||||
self.emergencyGeneratorContainer.hidden = NO;
|
||||
self.emergencyGeneratorContainer.frame = CGRectSetX( self.emergencyGeneratorContainer.frame,
|
||||
self.emergencyGeneratorContainer.frame.origin.x - 100 );
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
self.emergencyGeneratorContainer.frame = CGRectSetX( self.emergencyGeneratorContainer.frame,
|
||||
self.emergencyGeneratorContainer.frame.origin.x + 150 );
|
||||
self.emergencyGeneratorContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (!finished)
|
||||
return;
|
||||
|
||||
[self.emergencyName becomeFirstResponder];
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
self.emergencyGeneratorContainer.frame = CGRectSetX( self.emergencyGeneratorContainer.frame,
|
||||
self.emergencyGeneratorContainer.frame.origin.x - 50 );
|
||||
}];
|
||||
}];
|
||||
}
|
||||
if (motion == UIEventSubtypeMotionShake)
|
||||
[self emergencyOpenAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)marqueeTip {
|
||||
@@ -356,10 +357,11 @@
|
||||
self.selectedUser = nil;
|
||||
[self didToggleUserSelection];
|
||||
}
|
||||
else if ((self.selectedUser = user))
|
||||
[self didToggleUserSelection];
|
||||
else
|
||||
else if (!user)
|
||||
[self didSelectNewUserAvatar:avatar];
|
||||
else if ([self setSelectedUser:user])
|
||||
[self didToggleUserSelection];
|
||||
|
||||
} options:0];
|
||||
|
||||
[self.avatarToUserOID setObject:NilToNSNull([user objectID]) forKey:[NSValue valueWithNonretainedObject:avatar]];
|
||||
@@ -513,7 +515,6 @@
|
||||
self.nameLabel.center = CGPointMake( 160, 94 );
|
||||
self.nameLabel.backgroundColor = [UIColor blackColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor whiteColor];
|
||||
}
|
||||
else if (!selectedUser && self.passwordView.alpha == 1) {
|
||||
// User was just deselected.
|
||||
@@ -524,7 +525,6 @@
|
||||
self.nameLabel.center = CGPointMake( 160, 296 );
|
||||
self.nameLabel.backgroundColor = [UIColor clearColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor lightGrayColor];
|
||||
}
|
||||
|
||||
// Lay out the word wall.
|
||||
@@ -908,12 +908,20 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)emergencyClose:(UIButton *)sender {
|
||||
- (IBAction)emergencyOpen:(id)sender {
|
||||
|
||||
if ([sender isKindOfClass:[UIGestureRecognizer class]] && ((UIGestureRecognizer *)sender).state != UIGestureRecognizerStateBegan)
|
||||
return;
|
||||
|
||||
[self emergencyOpenAnimated:YES];
|
||||
}
|
||||
|
||||
- (IBAction)emergencyClose:(id)sender {
|
||||
|
||||
[self emergencyCloseAnimated:YES];
|
||||
}
|
||||
|
||||
- (IBAction)emergencyCopy:(UIButton *)sender {
|
||||
- (IBAction)emergencyCopy:(id)sender {
|
||||
|
||||
inf(@"Copying emergency password for: %@", self.emergencyName.text);
|
||||
[UIPasteboard generalPasteboard].string = [self.emergencyPassword titleForState:UIControlStateNormal];
|
||||
@@ -932,12 +940,44 @@
|
||||
}];
|
||||
|
||||
MPCheckpoint( MPCheckpointCopyToPasteboard, @{
|
||||
@"type" : [MPAlgorithmDefault nameOfType:self.emergencyType],
|
||||
@"type" : NilToNSNull([MPAlgorithmDefault nameOfType:self.emergencyType]),
|
||||
@"version" : @MPAlgorithmDefaultVersion,
|
||||
@"emergency" : @YES,
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)emergencyOpenAnimated:(BOOL)animated {
|
||||
|
||||
[[self.emergencyGeneratorContainer findFirstResponderInHierarchy] resignFirstResponder];
|
||||
|
||||
if (animated) {
|
||||
self.emergencyGeneratorContainer.alpha = 0;
|
||||
self.emergencyGeneratorContainer.hidden = NO;
|
||||
self.emergencyGeneratorContainer.frame = CGRectSetX( self.emergencyGeneratorContainer.frame,
|
||||
self.emergencyGeneratorContainer.frame.origin.x - 100 );
|
||||
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
self.emergencyGeneratorContainer.frame = CGRectSetX( self.emergencyGeneratorContainer.frame,
|
||||
self.emergencyGeneratorContainer.frame.origin.x + 150 );
|
||||
self.emergencyGeneratorContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[self emergencyOpenAnimated:NO];
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
self.emergencyGeneratorContainer.frame = CGRectSetX( self.emergencyGeneratorContainer.frame,
|
||||
self.emergencyGeneratorContainer.frame.origin.x - 50 );
|
||||
}];
|
||||
}
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
MPCheckpoint( MPCheckpointEmergencyGenerator, nil );
|
||||
self.emergencyGeneratorContainer.hidden = NO;
|
||||
self.emergencyGeneratorContainer.alpha = 1;
|
||||
[self.emergencyName becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)emergencyCloseAnimated:(BOOL)animated {
|
||||
|
||||
[[self.emergencyGeneratorContainer findFirstResponderInHierarchy] resignFirstResponder];
|
||||
@@ -946,7 +986,8 @@
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
self.emergencyGeneratorContainer.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[self emergencyCloseAnimated:NO];
|
||||
if (finished)
|
||||
[self emergencyCloseAnimated:NO];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
@@ -954,7 +995,7 @@
|
||||
self.emergencyName.text = @"";
|
||||
self.emergencyMasterPassword.text = @"";
|
||||
self.emergencySite.text = @"";
|
||||
self.emergencyCounterStepper.value = 0;
|
||||
self.emergencyCounterStepper.value = 1;
|
||||
[self.emergencyPassword setTitle:@"" forState:UIControlStateNormal];
|
||||
[self.emergencyActivity stopAnimating];
|
||||
self.emergencyGeneratorContainer.alpha = 0;
|
||||
@@ -1038,7 +1079,7 @@
|
||||
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||
}
|
||||
|
||||
- (IBAction)facebook:(UIButton *)sender {
|
||||
- (IBAction)facebook:(id)sender {
|
||||
|
||||
if (![SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {
|
||||
[PearlAlert showAlertWithTitle:@"Facebook Not Enabled" message:@"To send tweets, configure Facebook from Settings."
|
||||
@@ -1053,7 +1094,7 @@
|
||||
[self presentViewController:vc animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction)twitter:(UIButton *)sender {
|
||||
- (IBAction)twitter:(id)sender {
|
||||
|
||||
if (![SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) {
|
||||
[PearlAlert showAlertWithTitle:@"Twitter Not Enabled" message:@"To send tweets, configure Twitter from Settings."
|
||||
@@ -1068,19 +1109,19 @@
|
||||
[self presentViewController:vc animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction)google:(UIButton *)sender {
|
||||
- (IBAction)google:(id)sender {
|
||||
|
||||
id<GPPShareBuilder> shareDialog = [[GPPShare sharedInstance] shareDialog];
|
||||
[[[shareDialog setURLToShare:[NSURL URLWithString:@"http://masterpasswordapp.com"]]
|
||||
setPrefillText:@"I've started doing passwords properly thanks to Master Password."] open];
|
||||
}
|
||||
|
||||
- (IBAction)mail:(UIButton *)sender {
|
||||
- (IBAction)mail:(id)sender {
|
||||
|
||||
[[MPiOSAppDelegate get] showFeedbackWithLogs:NO forVC:self];
|
||||
}
|
||||
|
||||
- (IBAction)add:(UIButton *)sender {
|
||||
- (IBAction)add:(id)sender {
|
||||
|
||||
[PearlSheet showSheetWithTitle:@"Follow Master Password" viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
@@ -1158,7 +1199,10 @@
|
||||
return selectedUser;
|
||||
}
|
||||
|
||||
- (void)setSelectedUser:(MPUserEntity *)selectedUser {
|
||||
- (BOOL)setSelectedUser:(MPUserEntity *)selectedUser {
|
||||
|
||||
if ([_selectedUserOID isEqual:selectedUser.objectID])
|
||||
return NO;
|
||||
|
||||
NSError *error = nil;
|
||||
if (selectedUser.objectID.isTemporaryID &&
|
||||
@@ -1166,6 +1210,7 @@
|
||||
err(@"Failed to obtain a permanent object ID after setting selected user: %@", error);
|
||||
|
||||
_selectedUserOID = selectedUser.objectID;
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -23,29 +23,24 @@
|
||||
|
||||
+ (void)initialize {
|
||||
|
||||
[MPiOSConfig get];
|
||||
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].printLevel = PearlLogLevelDebug;
|
||||
//[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
|
||||
#else
|
||||
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
[[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
@try {
|
||||
[[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
@try {
|
||||
NSString *testFlightToken = [self testFlightToken];
|
||||
if ([testFlightToken length]) {
|
||||
inf(@"Initializing TestFlight");
|
||||
#ifdef ADHOC
|
||||
[TestFlight setDeviceIdentifier:[(id)[UIDevice currentDevice] uniqueIdentifier]];
|
||||
#else
|
||||
[TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
#endif
|
||||
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
|
||||
[TestFlight addCustomEnvironmentInformation:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
|
||||
[TestFlight setOptions:@{
|
||||
@@ -54,9 +49,7 @@
|
||||
}];
|
||||
[TestFlight takeOff:testFlightToken];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
PearlLogLevel level = PearlLogLevelWarn;
|
||||
if ([[MPiOSConfig get].sendInfo boolValue])
|
||||
level = PearlLogLevelInfo;
|
||||
PearlLogLevel level = PearlLogLevelDebug;
|
||||
|
||||
if (message.level >= level)
|
||||
TFLog( @"%@", [message messageDescription] );
|
||||
@@ -66,22 +59,13 @@
|
||||
TFLog( @"TestFlight (%@) initialized for: %@ v%@.", //
|
||||
TESTFLIGHT_SDK_VERSION, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion );
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"TestFlight: %@", exception);
|
||||
}
|
||||
#endif
|
||||
@try {
|
||||
NSString *googlePlusClientID = [self googlePlusClientID];
|
||||
if ([googlePlusClientID length]) {
|
||||
inf(@"Initializing Google+");
|
||||
[[GPPSignIn sharedInstance] setClientID:googlePlusClientID];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"Google+: %@", exception);
|
||||
}
|
||||
@try {
|
||||
#ifdef CRASHLYTICS
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
inf(@"Initializing Crashlytics");
|
||||
@@ -94,9 +78,9 @@
|
||||
[Crashlytics setObjectValue:@"Anonymous" forKey:@"username"];
|
||||
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
PearlLogLevel level = PearlLogLevelWarn;
|
||||
PearlLogLevel level = PearlLogLevelInfo;
|
||||
if ([[MPiOSConfig get].sendInfo boolValue])
|
||||
level = PearlLogLevelInfo;
|
||||
level = PearlLogLevelDebug;
|
||||
|
||||
if (message.level >= level)
|
||||
CLSLog( @"%@", [message messageDescription] );
|
||||
@@ -106,11 +90,8 @@
|
||||
CLSLog( @"Crashlytics (%@) initialized for: %@ v%@.", //
|
||||
[Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion );
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"Crashlytics: %@", exception);
|
||||
}
|
||||
@try {
|
||||
#endif
|
||||
#ifdef LOCALYTICS
|
||||
NSString *localyticsKey = [self localyticsKey];
|
||||
if ([localyticsKey length]) {
|
||||
inf(@"Initializing Localytics");
|
||||
@@ -124,164 +105,133 @@
|
||||
if (message.level >= PearlLogLevelWarn)
|
||||
MPCheckpoint( @"Problem", @{
|
||||
@"level" : @(PearlLogLevelStr( message.level )),
|
||||
@"message" : message.message
|
||||
@"message" : NilToNSNull(message.message)
|
||||
} );
|
||||
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"Localytics exception: %@", exception);
|
||||
err(@"During Analytics Setup: %@", exception);
|
||||
}
|
||||
@try {
|
||||
if (floor( NSFoundationVersionNumber ) <= NSFoundationVersionNumber_iOS_6_1) {
|
||||
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:
|
||||
@{
|
||||
UITextAttributeTextColor : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f],
|
||||
UITextAttributeTextShadowColor : [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f],
|
||||
UITextAttributeTextShadowOffset : [NSValue valueWithUIOffset:UIOffsetMake( 0, -1 )],
|
||||
UITextAttributeFont : [UIFont fontWithName:@"Exo-Bold" size:20.0f]
|
||||
}];
|
||||
|
||||
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:
|
||||
@{
|
||||
UITextAttributeTextColor : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f],
|
||||
UITextAttributeTextShadowColor : [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f],
|
||||
UITextAttributeTextShadowOffset : [NSValue valueWithUIOffset:UIOffsetMake( 0, -1 )],
|
||||
UITextAttributeFont : [UIFont fontWithName:@"Exo-Bold" size:20.0f]
|
||||
}];
|
||||
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:
|
||||
@{
|
||||
UITextAttributeTextColor : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f],
|
||||
UITextAttributeTextShadowColor : [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f],
|
||||
UITextAttributeTextShadowOffset : [NSValue valueWithUIOffset:UIOffsetMake( 0, 1 )]//,
|
||||
// Causes a bug in iOS where image views get oddly stretched... or something.
|
||||
//UITextAttributeFont: [UIFont fontWithName:@"HelveticaNeue" size:13.0f]
|
||||
}
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
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:
|
||||
@{
|
||||
UITextAttributeTextColor : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f],
|
||||
UITextAttributeTextShadowColor : [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f],
|
||||
UITextAttributeTextShadowOffset : [NSValue valueWithUIOffset:UIOffsetMake( 0, 1 )]//,
|
||||
// Causes a bug in iOS where image views get oddly stretched... or something.
|
||||
//UITextAttributeFont: [UIFont fontWithName:@"HelveticaNeue" size:13.0f]
|
||||
}
|
||||
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 *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"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
// UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
// UIImage *thumbImage = [UIImage imageNamed:@"slider-handle"];
|
||||
//
|
||||
// [[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
|
||||
// [[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
|
||||
// [[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
|
||||
//
|
||||
// UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
||||
// UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
|
||||
// UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns"];
|
||||
// UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel"];
|
||||
// UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns"];
|
||||
//
|
||||
// [[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];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
if ([[MPiOSConfig get].sendInfo boolValue]) {
|
||||
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
|
||||
[PearlLogger get].printLevel = PearlLogLevelInfo;
|
||||
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[self storeManager].cloudEnabled 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].showSetup 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"];
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([MPConfig get].rememberLogin)
|
||||
forKey:@"rememberLogin"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringB([self storeManager].cloudEnabled)
|
||||
forKey:@"iCloud"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([MPConfig get].iCloudDecided)
|
||||
forKey:@"iCloudDecided"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([MPiOSConfig get].sendInfo)
|
||||
forKey:@"sendInfo"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([MPiOSConfig get].helpHidden)
|
||||
forKey:@"helpHidden"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([MPiOSConfig get].showSetup)
|
||||
forKey:@"showQuickStart"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([PearlConfig get].firstRun)
|
||||
forKey:@"firstRun"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([PearlConfig get].launchCount)
|
||||
forKey:@"launchCount"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([PearlConfig get].askForReviews)
|
||||
forKey:@"askForReviews"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB([PearlConfig get].reviewAfterLaunches)
|
||||
forKey:@"reviewAfterLaunches"];
|
||||
[TestFlight addCustomEnvironmentInformation:[PearlConfig get].reviewedVersion
|
||||
forKey:@"reviewedVersion"];
|
||||
#endif
|
||||
MPCheckpoint( MPCheckpointConfig, @{
|
||||
@"rememberLogin" : @([[MPConfig get].rememberLogin boolValue]),
|
||||
@"iCloud" : @([self storeManager].cloudEnabled),
|
||||
@"iCloudDecided" : @([[MPConfig get].iCloudDecided boolValue]),
|
||||
@"sendInfo" : @([[MPiOSConfig get].sendInfo boolValue]),
|
||||
@"helpHidden" : @([[MPiOSConfig get].helpHidden boolValue]),
|
||||
@"showQuickStart" : @([[MPiOSConfig get].showSetup boolValue]),
|
||||
@"firstRun" : @([[PearlConfig get].firstRun boolValue]),
|
||||
@"launchCount" : NilToNSNull([PearlConfig get].launchCount),
|
||||
@"askForReviews" : @([[PearlConfig get].askForReviews boolValue]),
|
||||
@"reviewAfterLaunches" : NilToNSNull([PearlConfig get].reviewAfterLaunches),
|
||||
@"reviewedVersion" : NilToNSNull([PearlConfig get].reviewedVersion)
|
||||
} );
|
||||
}
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note userInfo:nil];
|
||||
}];
|
||||
// UIImage *minImage = [[UIImage imageNamed:@"slider-minimum"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
// UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
// UIImage *thumbImage = [UIImage imageNamed:@"slider-handle"];
|
||||
//
|
||||
// [[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
|
||||
// [[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
|
||||
// [[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
|
||||
//
|
||||
// UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
||||
// UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
|
||||
// UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns"];
|
||||
// UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel"];
|
||||
// UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns"];
|
||||
//
|
||||
// [[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];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Theme Setup: %@", exception);
|
||||
}
|
||||
@try {
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self checkConfig];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
|
||||
object:note userInfo:nil];
|
||||
}];
|
||||
|
||||
#ifdef ADHOC
|
||||
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
|
||||
@"Thank you for taking the time to test Master Password.\n\n"
|
||||
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
|
||||
@"Contact me directly at:\n"
|
||||
@"lhunath@lyndir.com\n"
|
||||
@"Or report detailed issues at:\n"
|
||||
@"https://youtrack.lyndir.com\n"
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
|
||||
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
|
||||
@"Thank you for taking the time to test Master Password.\n\n"
|
||||
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
|
||||
@"Contact me directly at:\n"
|
||||
@"lhunath@lyndir.com\n"
|
||||
@"Or report detailed issues at:\n"
|
||||
@"https://youtrack.lyndir.com\n"
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
|
||||
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
#endif
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Config Test: %@", exception);
|
||||
}
|
||||
@try {
|
||||
[super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Pearl Application Launch: %@", exception);
|
||||
}
|
||||
@try {
|
||||
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
|
||||
|
||||
[super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
if ([[MPiOSConfig get].showSetup boolValue])
|
||||
[[MPiOSAppDelegate get] showSetup];
|
||||
} );
|
||||
|
||||
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
|
||||
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
if ([[MPiOSConfig get].showSetup boolValue])
|
||||
[[MPiOSAppDelegate get] showSetup];
|
||||
} );
|
||||
|
||||
MPCheckpoint(MPCheckpointStarted, @{
|
||||
@"simulator" : PearlStringB([PearlDeviceUtils isSimulator]),
|
||||
@"encrypted" : PearlStringB([PearlDeviceUtils isAppEncrypted]),
|
||||
@"jailbroken" : PearlStringB([PearlDeviceUtils isJailbroken]),
|
||||
@"platform" : [PearlDeviceUtils platform],
|
||||
MPCheckpoint( MPCheckpointStarted, @{
|
||||
@"simulator" : PearlStringB( [PearlDeviceUtils isSimulator] ),
|
||||
@"encrypted" : PearlStringB( [PearlDeviceUtils isAppEncrypted] ),
|
||||
@"jailbroken" : PearlStringB( [PearlDeviceUtils isJailbroken] ),
|
||||
@"platform" : [PearlDeviceUtils platform],
|
||||
#ifdef APPSTORE
|
||||
@"legal" : PearlStringB([PearlDeviceUtils isAppEncrypted]),
|
||||
@"legal" : PearlStringB([PearlDeviceUtils isAppEncrypted]),
|
||||
#else
|
||||
@"legal" : @"YES",
|
||||
@"legal" : @"YES",
|
||||
#endif
|
||||
});
|
||||
} );
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Post-Startup: %@", exception);
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -395,24 +345,30 @@
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
#endif
|
||||
|
||||
[super applicationDidEnterBackground:application];
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] resume];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
#endif
|
||||
|
||||
[super applicationWillEnterForeground:application];
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
#endif
|
||||
|
||||
[super applicationWillTerminate:application];
|
||||
}
|
||||
@@ -423,8 +379,10 @@
|
||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||
[self signOutAnimated:NO];
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
#endif
|
||||
|
||||
[super applicationWillResignActive:application];
|
||||
}
|
||||
@@ -432,10 +390,13 @@
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
|
||||
inf(@"Re-activated");
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:application userInfo:nil];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
|
||||
object:application userInfo:nil];
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] resume];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
#endif
|
||||
|
||||
[super applicationDidBecomeActive:application];
|
||||
}
|
||||
@@ -618,17 +579,145 @@
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - PearlConfigDelegate
|
||||
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
|
||||
|
||||
if (configKey == @selector(traceMode)) {
|
||||
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
|
||||
inf(@"Trace is now: %@", [[MPiOSConfig get].traceMode boolValue]? @"ON": @"OFF");
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
|
||||
object:NSStringFromSelector( configKey ) userInfo:nil];
|
||||
}
|
||||
|
||||
- (void)checkConfig {
|
||||
|
||||
// iCloud enabled / disabled
|
||||
BOOL iCloudEnabled = [[MPiOSConfig get].iCloudEnabled boolValue];
|
||||
BOOL cloudEnabled = self.storeManager.cloudEnabled;
|
||||
if (iCloudEnabled != cloudEnabled) {
|
||||
if ([[MPiOSConfig get].iCloudEnabled boolValue])
|
||||
[self.storeManager setCloudEnabledAndOverwriteCloudWithLocalIfConfirmed:^(void (^setConfirmationAnswer)(BOOL answer)) {
|
||||
__block NSUInteger siteCount = NSNotFound;
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
||||
NSError *error = nil;
|
||||
if ((siteCount = [context countForFetchRequest:fetchRequest error:&error]) == NSNotFound) {
|
||||
wrn(@"Couldn't count current sites: %@", error);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
|
||||
// If we currently have no sites, don't bother asking to copy them.
|
||||
if (siteCount == 0) {
|
||||
setConfirmationAnswer( NO );
|
||||
return;
|
||||
}
|
||||
|
||||
// The current store has sites, ask the user if he wants to copy them to the cloud
|
||||
[PearlAlert showAlertWithTitle:@"Copy Sites To iCloud?"
|
||||
message:@"You can either switch to your old iCloud sites "
|
||||
@"or overwrite them with your current sites."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
setConfirmationAnswer( NO );
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
setConfirmationAnswer( YES );
|
||||
}
|
||||
cancelTitle:@"Use Old" otherTitles:@"Overwrite", nil];
|
||||
}];
|
||||
else
|
||||
[self.storeManager setCloudDisabledAndOverwriteLocalWithCloudIfConfirmed:^(void (^setConfirmationAnswer)(BOOL answer)) {
|
||||
__block NSUInteger siteCount = NSNotFound;
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
||||
NSError *error = nil;
|
||||
if ((siteCount = [context countForFetchRequest:fetchRequest error:&error]) == NSNotFound) {
|
||||
wrn(@"Couldn't count current sites: %@", error);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
|
||||
// If we currently have no sites, don't bother asking to copy them.
|
||||
if (siteCount == 0) {
|
||||
setConfirmationAnswer( NO );
|
||||
return;
|
||||
}
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Copy iCloud Sites?"
|
||||
message:@"You can either switch to the old sites on your device "
|
||||
@"or overwrite them with your current iCloud sites."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
setConfirmationAnswer( NO );
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
setConfirmationAnswer( YES );
|
||||
}
|
||||
cancelTitle:@"Use Old" otherTitles:@"Overwrite", nil];
|
||||
}];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey ) userInfo:nil];
|
||||
// Trace mode
|
||||
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
|
||||
|
||||
// Send info
|
||||
if ([[MPiOSConfig get].sendInfo boolValue]) {
|
||||
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
|
||||
[PearlLogger get].printLevel = PearlLogLevelInfo;
|
||||
|
||||
#ifdef CRASHLYTICS
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].iCloudEnabled boolValue] forKey:@"iCloudEnabled"];
|
||||
[[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].showSetup 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"];
|
||||
#endif
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPConfig get].rememberLogin )
|
||||
forKey:@"rememberLogin"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPiOSConfig get].iCloudEnabled )
|
||||
forKey:@"iCloudEnabled"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPConfig get].iCloudDecided )
|
||||
forKey:@"iCloudDecided"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPiOSConfig get].sendInfo )
|
||||
forKey:@"sendInfo"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPiOSConfig get].helpHidden )
|
||||
forKey:@"helpHidden"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPiOSConfig get].showSetup )
|
||||
forKey:@"showQuickStart"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [PearlConfig get].firstRun )
|
||||
forKey:@"firstRun"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [PearlConfig get].launchCount )
|
||||
forKey:@"launchCount"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [PearlConfig get].askForReviews )
|
||||
forKey:@"askForReviews"];
|
||||
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [PearlConfig get].reviewAfterLaunches )
|
||||
forKey:@"reviewAfterLaunches"];
|
||||
[TestFlight addCustomEnvironmentInformation:[PearlConfig get].reviewedVersion
|
||||
forKey:@"reviewedVersion"];
|
||||
#endif
|
||||
MPCheckpoint( MPCheckpointConfig, @{
|
||||
@"rememberLogin" : @([[MPConfig get].rememberLogin boolValue]),
|
||||
@"iCloudEnabled" : @([[MPiOSConfig get].iCloudEnabled boolValue]),
|
||||
@"iCloudDecided" : @([[MPConfig get].iCloudDecided boolValue]),
|
||||
@"sendInfo" : @([[MPiOSConfig get].sendInfo boolValue]),
|
||||
@"helpHidden" : @([[MPiOSConfig get].helpHidden boolValue]),
|
||||
@"showQuickStart" : @([[MPiOSConfig get].showSetup boolValue]),
|
||||
@"firstRun" : @([[PearlConfig get].firstRun boolValue]),
|
||||
@"launchCount" : NilToNSNull([PearlConfig get].launchCount),
|
||||
@"askForReviews" : @([[PearlConfig get].askForReviews boolValue]),
|
||||
@"reviewAfterLaunches" : NilToNSNull([PearlConfig get].reviewAfterLaunches),
|
||||
@"reviewedVersion" : NilToNSNull([PearlConfig get].reviewedVersion)
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -648,6 +737,7 @@
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator
|
||||
isCloud:(BOOL)isCloudStore {
|
||||
|
||||
[MPiOSConfig get].iCloudEnabled = @(isCloudStore);
|
||||
[super ubiquityStoreManager:manager didLoadStoreForCoordinator:coordinator isCloud:isCloudStore];
|
||||
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
@@ -676,24 +766,27 @@
|
||||
@"Waiting for your other device to auto‑correct the problem..."
|
||||
initAlert:^(UIAlertView *alert) {
|
||||
[alert addButtonWithTitle:@"Fix Now"];
|
||||
[alert addButtonWithTitle:@"Turn Off"];
|
||||
}];
|
||||
|
||||
self.handleCloudContentAlert.tappedButtonBlock = ^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
wSelf.fixCloudContentAlert = [PearlAlert showAlertWithTitle:@"Fix iCloud Now" message:
|
||||
@"This problem can be auto‑corrected by opening the app on another device where you recently made changes.\n"
|
||||
@"You can correct the problem from this device anyway, but recent changes made on another device might get lost.\n\n"
|
||||
@"You can also turn iCloud off and go back to your local sites."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:
|
||||
^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_ == alert_.cancelButtonIndex)
|
||||
[wSelf showCloudContentAlert];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex])
|
||||
[wSelf.storeManager rebuildCloudContentFromCloudStoreOrLocalStore:YES];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
wSelf.storeManager.cloudEnabled = NO;
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonBack otherTitles:@"Fix Anyway",
|
||||
@"Turn Off", nil];
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
wSelf.fixCloudContentAlert = [PearlAlert showAlertWithTitle:@"Fix iCloud Now" message:
|
||||
@"This problem can be auto‑corrected by opening the app on another device where you recently made changes.\n"
|
||||
@"You can fix the problem from this device anyway, but recent changes from another device might get lost.\n\n"
|
||||
@"You can also turn iCloud off for now."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:
|
||||
^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_ == alert_.cancelButtonIndex)
|
||||
[wSelf showCloudContentAlert];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex])
|
||||
[wSelf.storeManager rebuildCloudContentFromCloudStoreOrLocalStore:YES];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
[MPiOSConfig get].iCloudEnabled = NO;
|
||||
} cancelTitle:[PearlStrings get].commonButtonBack
|
||||
otherTitles:@"Fix Anyway", @"Turn Off", nil];
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||
[MPiOSConfig get].iCloudEnabled = NO;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -737,7 +830,7 @@
|
||||
NSString *testFlightToken = NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Application Token"]);
|
||||
if (![testFlightToken length])
|
||||
wrn(@"TestFlight token not set. Test Flight won't be aware of this test.");
|
||||
|
||||
|
||||
return testFlightToken;
|
||||
}
|
||||
|
||||
@@ -785,7 +878,7 @@
|
||||
#endif
|
||||
if (![localyticsKey length])
|
||||
wrn(@"Localytics key not set. Demographics won't be collected.");
|
||||
|
||||
|
||||
return localyticsKey;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
@property(nonatomic, retain) NSNumber *typeTipShown;
|
||||
@property(nonatomic, retain) NSNumber *loginNameTipShown;
|
||||
@property(nonatomic, retain) NSNumber *traceMode;
|
||||
@property(nonatomic, retain) NSNumber *iCloudEnabled;
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
@implementation MPiOSConfig
|
||||
|
||||
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode;
|
||||
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, iCloudEnabled;
|
||||
|
||||
- (id)init {
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
NSStringFromSelector( @selector(actionsTipShown) ) : @(!self.firstRun),
|
||||
NSStringFromSelector( @selector(typeTipShown) ) : @(!self.firstRun),
|
||||
NSStringFromSelector( @selector(loginNameTipShown) ) : @NO,
|
||||
NSStringFromSelector( @selector(traceMode) ) : @NO
|
||||
NSStringFromSelector( @selector(traceMode) ) : @NO,
|
||||
NSStringFromSelector( @selector(iCloudEnabled) ) : @NO
|
||||
}];
|
||||
|
||||
return self;
|
||||
|
||||
@@ -30,22 +30,9 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>Icon</string>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>Icon</string>
|
||||
<string>Icon-72</string>
|
||||
<string>Icon-Small</string>
|
||||
<string>Icon-Small-50</string>
|
||||
<string>Icon.png</string>
|
||||
<string>Icon@2x.png</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict/>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
<dict/>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.lyndir.lhunath.MasterPassword</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
|
||||
@@ -16,11 +16,17 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
#import "TestFlight.h"
|
||||
#ifdef CRASHLYTICS
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
#endif
|
||||
|
||||
#ifdef TESTFLIGHT
|
||||
#import "TestFlight.h"
|
||||
#endif
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
#import "LocalyticsSession.h"
|
||||
#define LOCALYTICS 1
|
||||
#define CRASHLYTICS 1
|
||||
#endif
|
||||
|
||||
#import "MPTypes.h"
|
||||
#import "MPiOSConfig.h"
|
||||
|
||||
@@ -59,21 +59,16 @@
|
||||
DA5E5C7517248959003798D8 /* sysendian.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C6C17248959003798D8 /* sysendian.h */; };
|
||||
DA5E5C7617248959003798D8 /* warn.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C6D17248959003798D8 /* warn.h */; };
|
||||
DA5E5C78172489FA003798D8 /* libscryptenc-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5E5C77172489FA003798D8 /* libscryptenc-ios.a */; };
|
||||
DA5E5D4C1724F616003798D8 /* Icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5D441724F616003798D8 /* Icon-72.png */; };
|
||||
DA5E5D4D1724F616003798D8 /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5D451724F616003798D8 /* Icon-72@2x.png */; };
|
||||
DA5E5D4E1724F616003798D8 /* Icon-Small-50.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5D461724F616003798D8 /* Icon-Small-50.png */; };
|
||||
DA5E5D4F1724F616003798D8 /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5D471724F616003798D8 /* Icon-Small-50@2x.png */; };
|
||||
DA5E5D501724F616003798D8 /* Icon-Small.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5D481724F616003798D8 /* Icon-Small.png */; };
|
||||
DA5E5D511724F616003798D8 /* Icon-Small@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5D491724F616003798D8 /* Icon-Small@2x.png */; };
|
||||
DA5E5D521724F616003798D8 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5D4A1724F616003798D8 /* Icon.png */; };
|
||||
DA5E5D531724F616003798D8 /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5D4B1724F616003798D8 /* Icon@2x.png */; };
|
||||
DA6701B816406A4100B61001 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701B716406A4100B61001 /* Accounts.framework */; settings = {ATTRIBUTES = (Required, ); }; };
|
||||
DA6701DE16406B7300B61001 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DD16406B7300B61001 /* Social.framework */; settings = {ATTRIBUTES = (Required, ); }; };
|
||||
DA6701E016406BB400B61001 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DF16406BB400B61001 /* AdSupport.framework */; settings = {ATTRIBUTES = (Required, ); }; };
|
||||
DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; };
|
||||
DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
|
||||
DA69540617D975D900BF294E /* icon_gears.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37841711E29500CF925C /* icon_gears.png */; };
|
||||
DA69540717D975D900BF294E /* icon_gears@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37851711E29500CF925C /* icon_gears@2x.png */; };
|
||||
DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA829E51159847E0002417D3 /* libFontReplacer.a */; };
|
||||
DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA945C8617E3F3FD0053236B /* Images.xcassets */; };
|
||||
DA95D5F214DF0B2C008D1B94 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */; };
|
||||
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
|
||||
DABD38DB1711E29700CF925C /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD360F1711E29400CF925C /* ui_background.png */; };
|
||||
@@ -142,10 +137,6 @@
|
||||
DABD395C1711E29700CF925C /* avatar-9@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36911711E29400CF925C /* avatar-9@2x.png */; };
|
||||
DABD395D1711E29700CF925C /* background.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36931711E29400CF925C /* background.png */; };
|
||||
DABD395E1711E29700CF925C /* background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36941711E29400CF925C /* background@2x.png */; };
|
||||
DABD395F1711E29700CF925C /* Default-568h.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36951711E29400CF925C /* Default-568h.png */; };
|
||||
DABD39601711E29700CF925C /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36961711E29400CF925C /* Default-568h@2x.png */; };
|
||||
DABD39611711E29700CF925C /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36971711E29400CF925C /* Default.png */; };
|
||||
DABD39621711E29700CF925C /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36981711E29400CF925C /* Default@2x.png */; };
|
||||
DABD39841711E29700CF925C /* Exo-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DABD36BC1711E29500CF925C /* Exo-Bold.otf */; };
|
||||
DABD39851711E29700CF925C /* Exo-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DABD36BD1711E29500CF925C /* Exo-ExtraBold.otf */; };
|
||||
DABD39861711E29700CF925C /* Exo-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DABD36BE1711E29500CF925C /* Exo-Regular.otf */; };
|
||||
@@ -408,6 +399,8 @@
|
||||
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>"; };
|
||||
DA340E9E17CD830E00712B77 /* TestFlight+AsyncLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestFlight+AsyncLogging.h"; sourceTree = "<group>"; };
|
||||
DA340E9F17CD830E00712B77 /* TestFlight+ManualSessions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestFlight+ManualSessions.h"; sourceTree = "<group>"; };
|
||||
DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = "<group>"; };
|
||||
DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = "<group>"; };
|
||||
DA3EF17915A47744003ABF4E /* Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -433,19 +426,12 @@
|
||||
DA5E5C6C17248959003798D8 /* sysendian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sysendian.h; sourceTree = "<group>"; };
|
||||
DA5E5C6D17248959003798D8 /* warn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = warn.h; sourceTree = "<group>"; };
|
||||
DA5E5C77172489FA003798D8 /* libscryptenc-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libscryptenc-ios.a"; path = "/Users/.z/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib/libscryptenc-ios.a"; sourceTree = "<absolute>"; };
|
||||
DA5E5D441724F616003798D8 /* Icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72.png"; sourceTree = "<group>"; };
|
||||
DA5E5D451724F616003798D8 /* Icon-72@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72@2x.png"; sourceTree = "<group>"; };
|
||||
DA5E5D461724F616003798D8 /* Icon-Small-50.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50.png"; sourceTree = "<group>"; };
|
||||
DA5E5D471724F616003798D8 /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
|
||||
DA5E5D481724F616003798D8 /* Icon-Small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small.png"; sourceTree = "<group>"; };
|
||||
DA5E5D491724F616003798D8 /* Icon-Small@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small@2x.png"; sourceTree = "<group>"; };
|
||||
DA5E5D4A1724F616003798D8 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; };
|
||||
DA5E5D4B1724F616003798D8 /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = "<group>"; };
|
||||
DA6701B716406A4100B61001 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; };
|
||||
DA6701DD16406B7300B61001 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
|
||||
DA6701DF16406BB400B61001 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; };
|
||||
DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
|
||||
DA829E51159847E0002417D3 /* libFontReplacer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFontReplacer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA945C8617E3F3FD0053236B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; 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; };
|
||||
DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
|
||||
@@ -581,10 +567,6 @@
|
||||
DABD36911711E29400CF925C /* avatar-9@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-9@2x.png"; sourceTree = "<group>"; };
|
||||
DABD36931711E29400CF925C /* background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = background.png; sourceTree = "<group>"; };
|
||||
DABD36941711E29400CF925C /* background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "background@2x.png"; sourceTree = "<group>"; };
|
||||
DABD36951711E29400CF925C /* Default-568h.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h.png"; sourceTree = "<group>"; };
|
||||
DABD36961711E29400CF925C /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; };
|
||||
DABD36971711E29400CF925C /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = "<group>"; };
|
||||
DABD36981711E29400CF925C /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = "<group>"; };
|
||||
DABD36BC1711E29500CF925C /* Exo-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-Bold.otf"; sourceTree = "<group>"; };
|
||||
DABD36BD1711E29500CF925C /* Exo-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-ExtraBold.otf"; sourceTree = "<group>"; };
|
||||
DABD36BE1711E29500CF925C /* Exo-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-Regular.otf"; sourceTree = "<group>"; };
|
||||
@@ -1503,14 +1485,7 @@
|
||||
DABD360D1711E29400CF925C /* Media */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA5E5D441724F616003798D8 /* Icon-72.png */,
|
||||
DA5E5D451724F616003798D8 /* Icon-72@2x.png */,
|
||||
DA5E5D461724F616003798D8 /* Icon-Small-50.png */,
|
||||
DA5E5D471724F616003798D8 /* Icon-Small-50@2x.png */,
|
||||
DA5E5D481724F616003798D8 /* Icon-Small.png */,
|
||||
DA5E5D491724F616003798D8 /* Icon-Small@2x.png */,
|
||||
DA5E5D4A1724F616003798D8 /* Icon.png */,
|
||||
DA5E5D4B1724F616003798D8 /* Icon@2x.png */,
|
||||
DA945C8617E3F3FD0053236B /* Images.xcassets */,
|
||||
DA5E5C3C1723681B003798D8 /* Square-bottom.png */,
|
||||
DA5A09E8171BB0F7005284AB /* unlocked.png */,
|
||||
DA5A09E9171BB0F7005284AB /* unlocked@2x.png */,
|
||||
@@ -1519,10 +1494,6 @@
|
||||
DABD360E1711E29400CF925C /* Automaton */,
|
||||
DABD366B1711E29400CF925C /* Avatars */,
|
||||
DABD36921711E29400CF925C /* Background */,
|
||||
DABD36951711E29400CF925C /* Default-568h.png */,
|
||||
DABD36961711E29400CF925C /* Default-568h@2x.png */,
|
||||
DABD36971711E29400CF925C /* Default.png */,
|
||||
DABD36981711E29400CF925C /* Default@2x.png */,
|
||||
DABD36BB1711E29500CF925C /* Fonts */,
|
||||
DABD36D91711E29500CF925C /* Insignia */,
|
||||
DABD38751711E29700CF925C /* Tooltips */,
|
||||
@@ -2353,6 +2324,8 @@
|
||||
DACA22BF1705DE9D002C6C22 /* TestFlight */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA340E9E17CD830E00712B77 /* TestFlight+AsyncLogging.h */,
|
||||
DA340E9F17CD830E00712B77 /* TestFlight+ManualSessions.h */,
|
||||
DACA22C11705DE9D002C6C22 /* TestFlight.h */,
|
||||
DACA22C31705DE9D002C6C22 /* libTestFlight.a */,
|
||||
);
|
||||
@@ -2955,6 +2928,16 @@
|
||||
CLASSPREFIX = MP;
|
||||
LastUpgradeCheck = 0500;
|
||||
ORGANIZATIONNAME = Lyndir;
|
||||
TargetAttributes = {
|
||||
DA5BFA43147E415C00F98B1E = {
|
||||
DevelopmentTeam = HL3Q45LX9N;
|
||||
SystemCapabilities = {
|
||||
com.apple.DataProtection = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = DA5BFA3E147E415C00F98B1E /* Build configuration list for PBXProject "MasterPassword-iOS" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
@@ -3107,6 +3090,7 @@
|
||||
DABD39041711E29700CF925C /* ui_panel_display@2x.png in Resources */,
|
||||
DABD391D1711E29700CF925C /* ui_spinner.png in Resources */,
|
||||
DABD391E1711E29700CF925C /* ui_spinner@2x.png in Resources */,
|
||||
DA69540617D975D900BF294E /* icon_gears.png in Resources */,
|
||||
DABD39271711E29700CF925C /* ui_textfield.png in Resources */,
|
||||
DABD39281711E29700CF925C /* ui_textfield@2x.png in Resources */,
|
||||
DABD39291711E29700CF925C /* ui_toolbar_container.png in Resources */,
|
||||
@@ -3151,13 +3135,10 @@
|
||||
DABD395C1711E29700CF925C /* avatar-9@2x.png in Resources */,
|
||||
DABD395D1711E29700CF925C /* background.png in Resources */,
|
||||
DABD395E1711E29700CF925C /* background@2x.png in Resources */,
|
||||
DABD395F1711E29700CF925C /* Default-568h.png in Resources */,
|
||||
DABD39601711E29700CF925C /* Default-568h@2x.png in Resources */,
|
||||
DABD39611711E29700CF925C /* Default.png in Resources */,
|
||||
DABD39621711E29700CF925C /* Default@2x.png in Resources */,
|
||||
DABD39841711E29700CF925C /* Exo-Bold.otf in Resources */,
|
||||
DABD39851711E29700CF925C /* Exo-ExtraBold.otf in Resources */,
|
||||
DABD39861711E29700CF925C /* Exo-Regular.otf in Resources */,
|
||||
DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */,
|
||||
DABD39871711E29700CF925C /* SourceCodePro-Black.otf in Resources */,
|
||||
DABD39881711E29700CF925C /* SourceCodePro-ExtraLight.otf in Resources */,
|
||||
DABD39A01711E29700CF925C /* icon_action.png in Resources */,
|
||||
@@ -3176,6 +3157,7 @@
|
||||
DABD3ABD1711E29800CF925C /* icon_play@2x.png in Resources */,
|
||||
DABD3ABE1711E29800CF925C /* icon_plus.png in Resources */,
|
||||
DABD3ABF1711E29800CF925C /* icon_plus@2x.png in Resources */,
|
||||
DA69540717D975D900BF294E /* icon_gears@2x.png in Resources */,
|
||||
DABD3B1C1711E29800CF925C /* icon_up.png in Resources */,
|
||||
DABD3B1D1711E29800CF925C /* icon_up@2x.png in Resources */,
|
||||
DABD3B3C1711E29800CF925C /* tip_alert_black.png in Resources */,
|
||||
@@ -3223,14 +3205,6 @@
|
||||
DA5A09EA171BB0F7005284AB /* unlocked.png in Resources */,
|
||||
DA5A09EB171BB0F7005284AB /* unlocked@2x.png in Resources */,
|
||||
DA5E5C3D1723681B003798D8 /* Square-bottom.png in Resources */,
|
||||
DA5E5D4C1724F616003798D8 /* Icon-72.png in Resources */,
|
||||
DA5E5D4D1724F616003798D8 /* Icon-72@2x.png in Resources */,
|
||||
DA5E5D4E1724F616003798D8 /* Icon-Small-50.png in Resources */,
|
||||
DA5E5D4F1724F616003798D8 /* Icon-Small-50@2x.png in Resources */,
|
||||
DA5E5D501724F616003798D8 /* Icon-Small.png in Resources */,
|
||||
DA5E5D511724F616003798D8 /* Icon-Small@2x.png in Resources */,
|
||||
DA5E5D521724F616003798D8 /* Icon.png in Resources */,
|
||||
DA5E5D531724F616003798D8 /* Icon@2x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -3262,8 +3236,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\" 2>/dev/null || 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)\ncommit=${description##*-g} commit=$((0x$commit))\nversion=${description%-g*} version=${version//-/.}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"${version//.}$commit\" # No separator between version and commit because I had already submitted a CFBundleVersion with a really high major. Cry.\nsetPlistWithKey CFBundleShortVersionString \"$version\"\n\nsetSettingWithTitle \"Build\" \"$commit\"\nsetSettingWithTitle \"Version\" \"$version\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
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\" 2>/dev/null || 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)\ncommit=${description##*-g} commit=$((0x$commit))\nversion=${description%-g*} version=${version//-/.}\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"${version//.}$commit\" # No separator between version and commit because I had already submitted a CFBundleVersion with a really high major. Cry.\nsetPlistWithKey CFBundleShortVersionString \"$version\"\n\nsetSettingWithTitle \"Build\" \"$commit\"\nsetSettingWithTitle \"Version\" \"$version\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n\nif [[ $DEPLOYMENT_LOCATION = YES ]]; then\n # This build is a release. Do some release checks.\n passed=1\n [[ $description != *-dirty ]] || \\\n { passed=0; echo >&2 \"ERROR: Cannot release a dirty version, first commit any changes.\"; }\n [[ $(PlistBuddy -c \"Print :'API Key'\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Crashlytics.plist\") ]] || \\\n { passed=0; echo >&2 \"ERROR: Cannot release: Crashlytics API key is missing.\"; }\n [[ $(PlistBuddy -c \"Print :'ClientID'\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Google+.plist\") ]] || \\\n { passed=0; echo >&2 \"ERROR: Cannot release: Google+ ClientID is missing.\"; }\n [[ $(PlistBuddy -c \"Print :'Key.distribution'\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Localytics.plist\") ]] || \\\n { passed=0; echo >&2 \"ERROR: Cannot release: Localytics distribution key is missing.\"; }\n (( passed )) || \\\n { echo >&2 \"Failed to pass release checks. Fix the above errors and re-try. Aborting.\"; exit 1; }\nfi";
|
||||
};
|
||||
DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@@ -3582,16 +3555,21 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
|
||||
CLANG_WARN_CXX0X_EXTENSIONS = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
|
||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Maarten Billemont (DWGU95U4ZD)";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherit)",
|
||||
@@ -3608,9 +3586,9 @@
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
|
||||
GCC_VERSION = "";
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES;
|
||||
GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
|
||||
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
@@ -3640,7 +3618,6 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "C1EC2842-EF33-4340-AC88-8A12B106413D";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -3651,11 +3628,17 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
CLANG_WARN_CXX0X_EXTENSIONS = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
|
||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
@@ -3673,13 +3656,16 @@
|
||||
"$(inherited)",
|
||||
"NDEBUG=1",
|
||||
"NS_BLOCK_ASSERTIONS=1",
|
||||
"TESTFLIGHT=1",
|
||||
"LOCALYTICS=1",
|
||||
"CRASHLYTICS=1",
|
||||
);
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
|
||||
GCC_VERSION = "";
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES;
|
||||
GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
|
||||
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
@@ -3708,7 +3694,7 @@
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -3719,10 +3705,15 @@
|
||||
DA5BFA6E147E415C00F98B1E /* Debug-iOS */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Maarten Billemont (DWGU95U4ZD)";
|
||||
EXCLUDED_SOURCE_FILE_NAMES = libTestFlight.a;
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "EBD2A2E4-AEC3-4EEA-8FF0-5F1A0D9FFA1C";
|
||||
SKIP_INSTALL = NO;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
@@ -3731,12 +3722,15 @@
|
||||
DA5BFA6F147E415C00F98B1E /* AdHoc-iOS */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont (HL3Q45LX9N)";
|
||||
EXCLUDED_SOURCE_FILE_NAMES = "";
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "20B988BD-7A10-49E6-9FF7-367BD5714A31";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "4CBD21E7-DB60-4F7F-80F8-98DFA83D2CE0";
|
||||
SKIP_INSTALL = NO;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
@@ -3767,11 +3761,17 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
CLANG_WARN_CXX0X_EXTENSIONS = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
|
||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
@@ -3789,13 +3789,15 @@
|
||||
"$(inherited)",
|
||||
"NDEBUG=1",
|
||||
"NS_BLOCK_ASSERTIONS=1",
|
||||
"CRASHLYTICS=1",
|
||||
"LOCALYTICS=1",
|
||||
);
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
|
||||
GCC_VERSION = "";
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
|
||||
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES;
|
||||
GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
|
||||
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
@@ -3824,7 +3826,7 @@
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -3835,12 +3837,15 @@
|
||||
DA95D60A14DF3F3B008D1B94 /* AppStore-iOS */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont (HL3Q45LX9N)";
|
||||
EXCLUDED_SOURCE_FILE_NAMES = libTestFlight.a;
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "3EF104A5-610A-429E-ACF0-232A2C8A7E1C";
|
||||
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "6C6B84DD-9D8F-4321-BD77-5F737DBE1778";
|
||||
SKIP_INSTALL = NO;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
|
||||
@@ -1,95 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Enable this setting to send us carefully anonymized information to help us diagnose and resolve issues you might experience in the future.</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<string>[auto]</string>
|
||||
<key>Title</key>
|
||||
<string>Version</string>
|
||||
<key>Key</key>
|
||||
<string>unset</string>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<string>[auto]</string>
|
||||
<key>Title</key>
|
||||
<string>Build</string>
|
||||
<key>Key</key>
|
||||
<string>unset</string>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<string>[auto]</string>
|
||||
<key>Title</key>
|
||||
<string>Copyright</string>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
<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>When enabled, you will not be logged out when switching out of the application. This will help you get to your passwords faster, but when someone finds your device unlocked, they can too.</string>
|
||||
<key>Title</key>
|
||||
<string>Master Password</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
<key>Key</key>
|
||||
<string>rememberLogin</string>
|
||||
<key>Title</key>
|
||||
<string>Stay logged in</string>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>FooterText</key>
|
||||
<string>Synchronizes your sites with your other Apple devices. It's also a good way of keeping automatic backups.</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>iCloud</string>
|
||||
<key>Key</key>
|
||||
<string>USMCloudEnabledKey</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>StringsTable</key>
|
||||
<string>Root</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Enable this setting to send us carefully anonymized information to help us diagnose and resolve issues you might experience in the future.</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<string>[auto]</string>
|
||||
<key>Title</key>
|
||||
<string>Version</string>
|
||||
<key>Key</key>
|
||||
<string>unset</string>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<string>[auto]</string>
|
||||
<key>Title</key>
|
||||
<string>Build</string>
|
||||
<key>Key</key>
|
||||
<string>unset</string>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<string>[auto]</string>
|
||||
<key>Title</key>
|
||||
<string>Copyright</string>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
<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>When enabled, you will not be logged out when switching out of the application. This will help you get to your passwords faster, but when someone finds your device unlocked, they can too.</string>
|
||||
<key>Title</key>
|
||||
<string>Master Password</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
<key>Key</key>
|
||||
<string>rememberLogin</string>
|
||||
<key>Title</key>
|
||||
<string>Stay logged in</string>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>FooterText</key>
|
||||
<string>Synchronizes your sites with your other Apple devices. It's also a good way of keeping automatic backups.</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>iCloud</string>
|
||||
<key>Key</key>
|
||||
<string>iCloudEnabled</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>StringsTable</key>
|
||||
<string>Root</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 290 KiB |
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "57x57",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "57x57",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@2x.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 82 KiB |
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"extent" : "full-screen",
|
||||
"minimum-system-version" : "7.0",
|
||||
"filename" : "Default@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"subtype" : "retina4",
|
||||
"filename" : "Default-568h@2x.png",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"orientation" : "portrait",
|
||||
"idiom" : "iphone",
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-568h@2x.png",
|
||||
"subtype" : "retina4",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 182 KiB |
BIN
MasterPassword/Resources/Media/iTunesArtwork@2x.png
Normal file
|
After Width: | Height: | Size: 312 KiB |
@@ -14,20 +14,18 @@ downsize() {
|
||||
|
||||
|
||||
|
||||
inf "Converting iTunesArtwork"
|
||||
inf "Converting appiconsets"
|
||||
icons=(
|
||||
[57]="Icon.png"
|
||||
[114]="Icon@2x.png"
|
||||
[72]="Icon-72.png"
|
||||
[120]="Icon-60@2x.png"
|
||||
[80]="Icon-40@2x.png"
|
||||
[144]="Icon-72@2x.png"
|
||||
[29]="Icon-Small.png"
|
||||
[58]="Icon-Small@2x.png"
|
||||
[50]="Icon-Small-50.png"
|
||||
[100]="Icon-Small-50@2x.png"
|
||||
[512]="iTunesArtwork"
|
||||
)
|
||||
for size in $(reverse "${!icons[@]}"); do
|
||||
file=${icons[size]}
|
||||
file=Images.xcassets/AppIcon.appiconset/${icons[size]}
|
||||
|
||||
if [[ "iTunesArtwork@2x" -nt $file ]]; then
|
||||
pinf "$file ($size px)"
|
||||
|
||||
@@ -75,7 +75,11 @@ header .container {
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
|
||||
background: radial-gradient(center, ellipse cover, rgba(0,0,0,0.3) 50%,rgba(0,0,0,0.8) 100%);
|
||||
background: gradient(radial, center center, 0px, center center, 100%, color-stop(50%,rgba(0,0,0,0.3)), color-stop(100%,rgba(0,0,0,0.8)));
|
||||
background: radial-gradient(ellipse at center, rgba(0,0,0,0.3) 50%,rgba(0,0,0,0.8) 100%);
|
||||
background: radial-gradient(70% 50% at 30% 50%, rgba(0,0,0,0.3) 50%, rgba(0,0,0,0.8) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#44000000', endColorstr='#cc000000',GradientType=1 );
|
||||
}
|
||||
header .background {
|
||||
position: absolute;
|
||||
@@ -105,7 +109,11 @@ header .background {
|
||||
}
|
||||
#algorithm header .container,
|
||||
#support header .container {
|
||||
background: radial-gradient(center, ellipse cover, rgba(0,0,0,0.3) 50%,rgba(0,0,0,0.8) 100%);
|
||||
background: gradient(radial, center center, 0px, center center, 100%, color-stop(50%,rgba(0,0,0,0.3)), color-stop(100%,rgba(0,0,0,0.8)));
|
||||
background: radial-gradient(ellipse at center, rgba(0,0,0,0.3) 50%,rgba(0,0,0,0.8) 100%);
|
||||
background: radial-gradient(50% 50% at 50% 30%, rgba(0,0,0,0.3) 50%, rgba(0,0,0,0.8) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#44000000', endColorstr='#cc000000',GradientType=1 );
|
||||
}
|
||||
header .content {
|
||||
position: absolute;
|
||||
|
||||
21
Site/2013-05/news-dev.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body, html {
|
||||
color: white;
|
||||
font-family: 'Copperplate', sans-serif;
|
||||
font-size: smaller;
|
||||
text-align: center;
|
||||
}
|
||||
*:link, *:visited {
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="mailto:masterpassword@lyndir.com">E-mail us</a> or open <a href="http://masterpasswordapp.com/support.html">support</a> if you have any issues.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,6 +16,6 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="mailto:masterpassword@lyndir.com">E-mail us</a> or open <a href="http://masterpasswordapp.com/support.html">support</a> if you have any issues.</p>
|
||||
<p></p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||