2
0

Compare commits

...

56 Commits
1.7 ... 2.0

Author SHA1 Message Date
Maarten Billemont
aa461b73c8 Fixed sending emails when the key window is an alert window + added coachmarks. 2014-04-24 21:43:47 -04:00
Maarten Billemont
e070082f4a Restructured passwords UI a bit for a saner nav bar. 2014-04-21 23:35:29 -04:00
Maarten Billemont
079434d62b Fixed pull-down, added sections for settings and logs. 2014-04-20 11:09:49 -04:00
Maarten Billemont
f57de77545 Permanent IDs are now properly generated before save. 2014-04-15 01:07:46 -04:00
Maarten Billemont
18657271ba Fixed MOC hierarchy, saving and permanent object ID resolution. 2014-04-15 00:26:13 -04:00
Maarten Billemont
11d1dc711d Remove status bar from login screen. 2014-04-13 15:45:08 -04:00
Maarten Billemont
965d5efe7f Dismiss emergency generator when deactivated. 2014-04-13 14:06:33 -04:00
Maarten Billemont
87b01fcaaf Operational emergency generator. 2014-04-13 13:04:18 -04:00
Maarten Billemont
b2624c7572 Emergency generator, avatar change, improvements.
[ADDED]     Ability to change avatar while creating new user.
[FIXED]     Transition oddness.
[IMPROVED]  Remove passwordsVC while not needed.
[ADDED]     Emergency generator.
2014-04-12 14:43:41 -04:00
Maarten Billemont
bd37f1d6a7 Completed password cell handling and misc UI and moc update improvements.
[UPDATED]   Make private moc parent of all private blocks to avoid blocking the main thread for writes, update the main moc on private moc updates.
[FIXED]     Don't cause a crash on elements with a bad type.
[UPDATED]   Improved cell handling and UI update handling.
[UPDATED]   Replace FontReplacer with moarfonts to fix issues in UICollectionViewCells.
2014-04-06 23:34:18 -04:00
Maarten Billemont
f475c15360 Dismiss search when tapped outside. 2014-03-20 16:49:33 -04:00
Maarten Billemont
d3d4aeea41 Fix avatar item size to collection view size. 2014-03-20 08:42:15 -04:00
Maarten Billemont
5913ce80e5 full-height avatar collection view + iphone 4 height/keyboard fix. 2014-03-20 08:13:06 -04:00
Maarten Billemont
4c8bed2826 New passwords display in collection view. 2014-03-20 07:15:37 -04:00
Maarten Billemont
d036b43d6f Updated animations for activation of the passwords VC and fancier focussed user. 2014-03-19 20:09:25 -04:00
Maarten Billemont
318aca4d8f WIP - new UI for Master Password. 2014-03-15 20:38:14 -04:00
Maarten Billemont
060c9f91f3 "gmail.com" in guide search bar, iCloud description and tip tooltip.
[UPDATED]   Layout of setup on 4" iPhones.
[UPDATED]   iCloud description text.
[UPDATED]   Show "gmail.com" in the guide's search bar.
[ADDED]     A tooltip on the tip button.
2014-03-08 16:37:36 -05:00
Maarten Billemont
4184f609d6 Fixed updating search results when query is no match or element deleted.
[FIXED]     Make search results empty when no results.
[FIXED]     Update search results upon deletion.
2014-02-28 20:44:29 -05:00
Maarten Billemont
658d710847 Cloud and initial window improvements.
[ADDED]     Advanced option to mark self as corrupt.
[ADDED]     Tell user initial cloud sync can take a moment.
[IMPROVED]  Initial window now visible on full-screen spaces.
[FIXED]     User name label.
2014-02-22 18:27:14 -05:00
Maarten Billemont
3fa9843855 Improved site operations UI.
[IMPROVED]  New UI for site operations.
[ADDED]     Ability to delete a site.
2014-02-21 00:19:24 -05:00
Maarten Billemont
de3f51b447 Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-02-20 07:29:27 -05:00
Maarten Billemont
43c32e0f4c Improved input handling. 2014-02-20 07:29:05 -05:00
Maarten Billemont
775a6fd4ea Collection-view for elements and swipe to modify.
[ADDED]     NSCollectionView for navigating between elements.
[ADDED]     Mac: Functional buttons for changing type, loginName and counter.
2014-02-19 00:59:57 -05:00
Maarten Billemont
4f594c8c1d Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-02-13 17:53:42 -05:00
Maarten Billemont
ebadac8cd8 "Get it for" on new site pages. 2014-02-13 17:53:06 -05:00
Maarten Billemont
c48bed5ebd Add license to site. 2014-02-13 07:44:02 -05:00
Maarten Billemont
4f3efde6f0 Rewrite Mac UI for better multiple site handling. 2014-02-12 00:13:12 -05:00
Maarten Billemont
5b4e86a90a iOSPorts is no longer a requirement + fix some Pearl API. 2014-02-11 08:28:45 -05:00
Maarten Billemont
2be83752db Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-02-10 22:45:13 -05:00
Maarten Billemont
bd1a0f4e25 Fix Mac provisioning. 2014-02-10 22:45:05 -05:00
Maarten Billemont
70cd397591 Get it links on the site. 2014-02-10 09:25:35 -05:00
Maarten Billemont
1120529e34 Added an FAQ page to the homepage.
[ADDED]     FAQ: Answers common questions, most importantly, how to pick a master password.
2014-02-09 22:26:12 -05:00
Maarten Billemont
f57415a08b Security page improvements.
[ADDED]     Table of contents.
[FIXED]     Inline header style fixes.
[ADDED]     Trade-offs.
2014-02-09 17:57:20 -05:00
Maarten Billemont
f9da568bfd Security page improvements.
[UPDATED]   Linguistic improvements to security page.
[ADDED]     Explanation why MP is cryptographically secure.
[ADDED]     Paragraph about Silent Circle and Lavabit to trust section.
[ADDED]     Summary of MP's security features.
[ADDED]     Brief prelude about account security.
2014-02-09 15:44:12 -05:00
Maarten Billemont
39c9f8c5a0 Explain all the security properties of the Master Password solution.
[ADDED]     security.html to the site.
2014-02-07 01:02:43 -05:00
Maarten Billemont
a645e22973 Bump Crashlytics. 2014-01-26 18:57:26 -05:00
Maarten Billemont
eaf86d3348 Use gittip.com instead of Love Lyndir. 2014-01-26 18:55:08 -05:00
Maarten Billemont
2565321a4a Bump TestFlightSDK. 2014-01-26 18:48:05 -05:00
Maarten Billemont
ec828a82fd Build Master Password iOS scheme. 2014-01-26 12:14:33 -05:00
Maarten Billemont
411aa41226 Remove private 'Press' repo. 2014-01-26 12:09:36 -05:00
Maarten Billemont
00b164058d Check out dependencies using script, not git submodule init directly. 2014-01-26 12:03:23 -05:00
Maarten Billemont
9808613c75 Prepare for Travis-CI. 2014-01-26 11:57:57 -05:00
Maarten Billemont
1d20d81652 Bump libraries. 2013-12-23 10:31:06 -05:00
Maarten Billemont
f5c66ff35a Merge branch 'master' of github.com:Lyndir/MasterPassword
Conflicts:
	.gitmodules
2013-12-19 08:17:29 -05:00
Maarten Billemont
4c3d3234f5 Fixed submodule path for LoveLyndir. 2013-12-19 08:16:39 -05:00
Maarten Billemont
d543173b18 API and libraries bump. 2013-12-16 09:07:01 -05:00
Maarten Billemont
d8578e0162 USM fix and misc fixes.
[UPDATED]   UbiquityStoreManager to fix "bad file descriptor" errors.
[FIXED]     Crashlytics script when plist is empty.
[FIXED]     Missing DCIntrospect in updateDependencies.
[UPDATED]   Project configuration.
2013-12-03 20:34:48 -05:00
Maarten Billemont
d665833eba Add masterpassword CLI tool to website. 2013-12-03 20:29:26 -05:00
Maarten Billemont
33e25a5fed CLI tool install script.
[UPDATED]   Templates are now in ciphers.plist.
[ADDED]     Install script for Java-based mpw CLI tool.
[UPDATED]   Java tool is now bundled in a single executable self-containing JAR.
2013-12-03 08:16:32 -05:00
Maarten Billemont
c0737de939 Fix LoveLyndir submodule checkout. 2013-11-29 20:43:19 -05:00
Maarten Billemont
a18679eba8 Fix typo. 2013-11-17 12:49:15 -05:00
Maarten Billemont
ce321aeceb Make sure symbols of libs still exist in .app.
[FIXED]     Don't strip libs, only .app.
2013-11-16 16:36:46 -05:00
Maarten Billemont
6074547f64 Small fixes and privacy policy.
[UPDATED]   Reload users if the selected user can't be found.
[ADDED]     Privacy policy to the website.
[UPDATED]   Pearl - PearlOverlay from main thread.
2013-11-16 16:35:14 -05:00
Maarten Billemont
8432932cb7 Improved icons. 2013-11-09 21:14:23 -05:00
Maarten Billemont
f8dccc04d7 Minor versions 0-padded to 2 digits.
[FIXED]     Automatic app version calculator broke for minor versions < 10... moar hacks.
2013-11-09 21:04:47 -05:00
Maarten Billemont
b38ef59c93 Cleaner iOS icon for iOS 7. 2013-11-09 20:50:53 -05:00
150 changed files with 13501 additions and 4568 deletions

6
.gitmodules vendored
View File

@@ -13,9 +13,9 @@
[submodule "External/RHStatusItemView"] [submodule "External/RHStatusItemView"]
path = External/RHStatusItemView path = External/RHStatusItemView
url = git://github.com/lhunath/RHStatusItemView.git url = git://github.com/lhunath/RHStatusItemView.git
[submodule "External/LoveLyndir"]
path = External/LoveLyndir
url = git://github.com/Lyndir/love-lyndir.client.git
[submodule "External/DCIntrospect"] [submodule "External/DCIntrospect"]
path = External/DCIntrospect path = External/DCIntrospect
url = https://github.com/lhunath/DCIntrospect.git url = https://github.com/lhunath/DCIntrospect.git
[submodule "External/LoveLyndir"]
path = External/DCIntrospect
url = git://github.com/Lyndir/love-lyndir.client.git

View File

@@ -9,6 +9,7 @@
<inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <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="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SignednessMismatch" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnavailableInDeploymentTarget" enabled="true" level="INFO" enabled_by_default="true" /> <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="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />

7
.travis.yml Normal file
View File

@@ -0,0 +1,7 @@
language: objective-c
xcode_workspace: MasterPassword.xcworkspace
xcode_scheme: MasterPassword iOS (Development)
xcode_sdk: iphonesimulator
git:
submodules: false
before_install: ./Scripts/updateDependencies

View File

@@ -15,13 +15,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.1.3</string> <string>2.1.7</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>13</string> <string>26</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>

Binary file not shown.

Submodule External/FontReplacer deleted from 4e3dea0870

2
External/Pearl vendored

View File

@@ -7,7 +7,7 @@ CF_EXTERN_C_BEGIN
/*! /*!
\brief The Reveal Log level bit flags. \brief The Reveal Log level bit flags.
\discussion These flags are addative. Ie, you should bitwise OR them together. \discussion These flags are additive. i.e. you should bitwise OR them together.
\seealso IBARevealLoggerSetLevelMask \seealso IBARevealLoggerSetLevelMask
\seealso IBARevealLoggerGetLevelMask \seealso IBARevealLoggerGetLevelMask

Binary file not shown.

View File

@@ -6,12 +6,11 @@ The SDK can track more information if you pass it to TestFlight. The Checkpoint
The SDK also offers a remote logging solution. Find out more about our logging system in the "Remote Logging" section. The SDK also offers a remote logging solution. Find out more about our logging system in the "Remote Logging" section.
## Requirements ## Requirements
The TestFlight SDK requires iOS 4.3 or above, the Apple LLVM compiler, and the libz library to run. The TestFlight SDK requires iOS 4.3 or above, the Apple LLVM compiler, and the libz library to run.
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
@@ -40,9 +39,9 @@ The AdSupport.framework is required for iOS 6.0+ in order to uniquely identify u
4. Get your App Token 4. Get your App Token
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/](). 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/](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. Otherwise, if you have previously uploaded your app to TestFlight, go to your list of applications ([http://testflightapp.com/dashboard/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. 2. Click on the "App Token" tab on the left. The App Token for that application will be there.
@@ -92,7 +91,9 @@ After you have integrated the SDK into your application you need to upload your
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. 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. Sessions automatically start at when the app becomes active and end when the app resigns active. Sessions that start shortly after an end continue the session instead of starting a new one.
NB: Sessions do not start when `takeOff:` is called, `takeOff:` registers callbacks to start sessions when the app is active.
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). 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).
@@ -125,6 +126,8 @@ Use `passCheckpoint:` to track when a user performs certain tasks in your applic
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). 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).
NB: Checkpoints are only recorded during sessions.
### Custom Environment Information ### Custom Environment Information
@@ -162,6 +165,8 @@ For even better information in your remote logs, such as file name and line numb
Which will produce output that looks like Which will produce output that looks like
-[MyAppDelegate application:didFinishLaunchingWithOptions:] [Line 45] Launched! -[MyAppDelegate application:didFinishLaunchingWithOptions:] [Line 45] Launched!
NB: Logs are only recorded during sessions.
**Custom Logging** **Custom Logging**

View File

@@ -6,7 +6,7 @@
// Copyright 2011 TestFlight. All rights reserved. // Copyright 2011 TestFlight. All rights reserved.
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#define TESTFLIGHT_SDK_VERSION @"2.0.0" #define TESTFLIGHT_SDK_VERSION @"2.1.4"
#undef TFLog #undef TFLog
#if __cplusplus #if __cplusplus
@@ -41,7 +41,12 @@ extern "C" {
/** /**
* Starts a TestFlight session using the Application Token for this Application * Sets up TestFlight's infrastructure.
*
* - Saves App Token
* - Starts automatic session management
* - Installs Crash Handlers
* - Kicks off sending of old session data
* *
* @param applicationToken Will be the application token for the current application. * @param applicationToken Will be the application token for the current application.
* The token for this application can be retrieved by going to https://testflightapp.com/dashboard/applications/ * The token for this application can be retrieved by going to https://testflightapp.com/dashboard/applications/

Binary file not shown.

View File

@@ -1,4 +1,34 @@
## 2.0 ## 2.1.4
- Consolidate both SDK versions into one which removes all access to `ASIdentifierManager`
## 2.1.3
- Fix bug in 2.1.2-noadid which caused adid to be collected
## 2.1.2
- Fix for bug that caused events to not get sent properly when using the `TFOptionSessionKeepAliveTimeout` option
- Fix for bug that caused logs that were sent immediately after start session to sometimes not be sent to server
## 2.1.1
- Create sdk version that removes all access to `ASIdentifierManager`
- Add UIDevice's `identifierForVendor`
## 2.1
- Full support for the iPhone 5s ARM64 processor while still supporting down to iOS 4.3
## 2.0.2
- Fixed a bug where the sdk would cause an app's CPU usage to rise significantly if the device had no internet connection when the app started
## 2.0.1
- Fixed rare `8badf00d` crash in TFNetworkManager that happened when the app was in the background
## 2.0 - August 12, 2013
Improvements Improvements

View File

@@ -5,13 +5,11 @@
<key>IDESourceControlProjectFavoriteDictionaryKey</key> <key>IDESourceControlProjectFavoriteDictionaryKey</key>
<false/> <false/>
<key>IDESourceControlProjectIdentifier</key> <key>IDESourceControlProjectIdentifier</key>
<string>D4AB9F0C-D746-4319-AABF-B24705099AED</string> <string>3D68B2F1-988A-48C3-8450-B37D43BDFE92</string>
<key>IDESourceControlProjectName</key> <key>IDESourceControlProjectName</key>
<string>MasterPassword</string> <string>MasterPassword</string>
<key>IDESourceControlProjectOriginsDictionary</key> <key>IDESourceControlProjectOriginsDictionary</key>
<dict> <dict>
<key>2B6DA448-3730-4F84-B2C3-51272E0D42F3</key>
<string>ssh://github.com/lhunath/DCIntrospect.git</string>
<key>5263993D-5FE8-464F-B66E-B0F7C2DFF410</key> <key>5263993D-5FE8-464F-B66E-B0F7C2DFF410</key>
<string>ssh://github.com/lhunath/UbiquityStoreManager.git</string> <string>ssh://github.com/lhunath/UbiquityStoreManager.git</string>
<key>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</key> <key>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</key>
@@ -24,8 +22,10 @@
<string>git://github.com/lhunath/uicolor-utilities.git</string> <string>git://github.com/lhunath/uicolor-utilities.git</string>
<key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key> <key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key>
<string>git://github.com/lhunath/RHStatusItemView.git</string> <string>git://github.com/lhunath/RHStatusItemView.git</string>
<key>CBA93B91-B799-4CC6-85B6-749792B76DD4</key> <key>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</key>
<string>ssh://github.com/lhunath/InAppSettingsKit.git</string> <string>git://github.com/lhunath/InAppSettingsKit.git</string>
<key>D5CE8AB8-2F69-4A08-A2CE-93C70E0F0567</key>
<string>https://github.com/lhunath/DCIntrospect.git</string>
<key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key> <key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key>
<string>git://github.com/jonmarimba/jrswizzle.git</string> <string>git://github.com/jonmarimba/jrswizzle.git</string>
<key>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</key> <key>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</key>
@@ -35,8 +35,6 @@
<string>MasterPassword.xcworkspace</string> <string>MasterPassword.xcworkspace</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key> <key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict> <dict>
<key>2B6DA448-3730-4F84-B2C3-51272E0D42F3</key>
<string>../External/DCIntrospect</string>
<key>5263993D-5FE8-464F-B66E-B0F7C2DFF410</key> <key>5263993D-5FE8-464F-B66E-B0F7C2DFF410</key>
<string>../External/UbiquityStoreManager</string> <string>../External/UbiquityStoreManager</string>
<key>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</key> <key>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</key>
@@ -49,8 +47,10 @@
<string>../External/Pearl/External/uicolor-utilities</string> <string>../External/Pearl/External/uicolor-utilities</string>
<key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key> <key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key>
<string>../External/RHStatusItemView</string> <string>../External/RHStatusItemView</string>
<key>CBA93B91-B799-4CC6-85B6-749792B76DD4</key> <key>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</key>
<string>../External/InAppSettingsKit</string> <string>../External/InAppSettingsKit</string>
<key>D5CE8AB8-2F69-4A08-A2CE-93C70E0F0567</key>
<string>../External/DCIntrospect</string>
<key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key> <key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key>
<string>../External/Pearl/External/jrswizzle</string> <string>../External/Pearl/External/jrswizzle</string>
<key>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</key> <key>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</key>
@@ -68,7 +68,7 @@
<key>IDESourceControlRepositoryExtensionIdentifierKey</key> <key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string> <string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key> <key>IDESourceControlWCCIdentifierKey</key>
<string>2B6DA448-3730-4F84-B2C3-51272E0D42F3</string> <string>D5CE8AB8-2F69-4A08-A2CE-93C70E0F0567</string>
<key>IDESourceControlWCCName</key> <key>IDESourceControlWCCName</key>
<string>DCIntrospect</string> <string>DCIntrospect</string>
</dict> </dict>
@@ -84,7 +84,7 @@
<key>IDESourceControlRepositoryExtensionIdentifierKey</key> <key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string> <string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key> <key>IDESourceControlWCCIdentifierKey</key>
<string>CBA93B91-B799-4CC6-85B6-749792B76DD4</string> <string>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</string>
<key>IDESourceControlWCCName</key> <key>IDESourceControlWCCName</key>
<string>InAppSettingsKit</string> <string>InAppSettingsKit</string>
</dict> </dict>

View File

View File

@@ -32,10 +32,16 @@ public class MPTemplates extends MetaObject {
this.templates = templates; this.templates = templates;
} }
public static MPTemplates load() {
return loadFromPList( "ciphers.plist" );
}
public static MPTemplates loadFromPList(final String templateResource) { public static MPTemplates loadFromPList(final String templateResource) {
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed") @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
InputStream templateStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( templateResource ); InputStream templateStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( templateResource );
Preconditions.checkNotNull( templateStream, "Not found: %s", templateResource );
try { try {
NSObject plistObject = PropertyListParser.parse( templateStream ); NSObject plistObject = PropertyListParser.parse( templateStream );
Preconditions.checkState( NSDictionary.class.isAssignableFrom( plistObject.getClass() ) ); Preconditions.checkState( NSDictionary.class.isAssignableFrom( plistObject.getClass() ) );
@@ -98,6 +104,6 @@ public class MPTemplates extends MetaObject {
public static void main(final String... arguments) { public static void main(final String... arguments) {
loadFromPList( "templates.plist" ); load();
} }
} }

View File

@@ -31,7 +31,7 @@ public abstract class MasterPassword {
private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN; private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
private static final MessageDigests MP_hash = MessageDigests.SHA256; private static final MessageDigests MP_hash = MessageDigests.SHA256;
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256; private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
private static final MPTemplates templates = MPTemplates.loadFromPList( "templates.plist" ); private static final MPTemplates templates = MPTemplates.load();
public static byte[] keyForPassword(final String password, final String username) { public static byte[] keyForPassword(final String password, final String username) {

View File

@@ -29,30 +29,49 @@
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-antrun-plugin</artifactId>
<configuration> <version>1.7</version>
<archive> <executions>
<manifest> <execution>
<mainClass>com.lyndir.lhunath.masterpassword.CLI</mainClass> <id>prepare-package</id>
<addClasspath>true</addClasspath> <phase>prepare-package</phase>
<classpathPrefix>lib/</classpathPrefix> <configuration>
</manifest> <target>
</archive> <chmod file="${project.build.directory}/install" perm="755"/>
</configuration> </target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>2.4</version> <version>2.2</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory> <transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.lyndir.lhunath.masterpassword.CLI</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
#
# Install the Master Password CLI tool.
set -e
cd "${BASH_SOURCE%/*}"
source bashlib
inf "This will install the mpw tool."
# Try to guess then ask for the bin dir to install to.
IFS=: read -a paths <<< "$PATH"
if inArray ~/bin "${paths[@]}"; then
bindir=~/bin
elif inArray ~/.bin "${paths[@]}"; then
bindir=~/.bin
elif inArray /usr/local/bin "${paths[@]}"; then
bindir=/usr/local/bin
else
bindir=~/bin
fi
bindir=$(ask -d "$bindir" "What bin directory should I install to?")
[[ -d "$bindir" ]] || mkdir "$bindir" || ftl 'Cannot create missing bin directory: %s' "$bindir" || exit
[[ -w "$bindir" ]] || ftl 'Cannot write to bin directory: %s' "$bindir" || exit
# Try to guess then ask for the share dir to install to.
sharedir=$(cd -P "$bindir/.."; [[ $bindir = */.bin ]] && printf '%s/.share' "$PWD" || printf '%s/share' "$PWD")
sharedir=$(ask -d "$sharedir" "What share directory should I install to?")
[[ -d "$sharedir" ]] || mkdir "$sharedir" || ftl 'Cannot create missing share directory: %s' "$sharedir" || exit
[[ -w "$sharedir" ]] || ftl 'Cannot write to share directory: %s' "$sharedir" || exit
# Install Master Password.
sharepath=$sharedir/masterpassword
mkdir -p "$sharepath"
cp -a "masterpassword-cli-"*".jar" bashlib mpw "$sharepath"
ex -c "%s~%SHAREPATH%~$sharepath~g|x" "$sharepath/mpw"
ln -sf "$sharepath/mpw" "$bindir/mpw"
chmod +x "$sharepath/mpw"
[[ ! -e "$bindir/bashlib" ]] && ln -s "$sharepath/bashlib" "$bindir/bashlib" ||:
# Convenience bash function.
inf "Installation successful!"
echo
inf "To improve usability, you can install an mpw function in your bash shell."
inf "This function adds the following features:"
inf " - Ask the Master Password once, remember in the shell."
inf " - Automatically put the password in the clipboard (some platforms)."
echo
inf "To do this you need the following function in ~/.bashrc:\n%s" "$(<mpw.bashrc)"
echo
inf "We can do this for you automatically now."
if ask -c Y!n "Append the mpw function to your .bashrc?"; then
cat mpw.bashrc >> ~/.bashrc
inf "Done! Don't forget to run '%s' to apply the changes!" "source ~/.bashrc"
fi
echo
inf "To begin using Master Password, type: mpw [site name]"

View File

@@ -5,5 +5,5 @@
# Uncomment this to hardcode your master password. Make sure this file's permissions are tight. Master Password will not ask you for your master password anymore. This is probably not a good idea. # Uncomment this to hardcode your master password. Make sure this file's permissions are tight. Master Password will not ask you for your master password anymore. This is probably not a good idea.
# export MP_PASSWORD="banana colored duckling" # export MP_PASSWORD="banana colored duckling"
cd "${BASH_SOURCE[0]%/*}" cd "%SHAREPATH%"
java -jar masterpassword-cli-GIT-SNAPSHOT.jar "$@" exec java -jar masterpassword-cli-GIT-SNAPSHOT.jar "$@"

View File

@@ -0,0 +1,15 @@
source bashlib
mpw() {
_nocopy() { echo >&2 "$(cat)"; }
_copy() { "$(type -P pbcopy || type -P xclip || echo _nocopy)"; }
# Empty the clipboard
:| _copy 2>/dev/null
# Ask for the user's name and password if not yet known.
MP_USERNAME=${MP_USERNAME:-$(ask -s 'Your Full Name:')}
MP_PASSWORD=${MP_PASSWORD:-$(ask -s 'Master Password:')}
# Start Master Password and copy the output.
printf %s "$(MP_USERNAME=$MP_USERNAME MP_PASSWORD=$MP_PASSWORD command mpw "$@")" | _copy
}

View File

@@ -21,7 +21,7 @@
<modules> <modules>
<module>masterpassword-algorithm</module> <module>masterpassword-algorithm</module>
<module>masterpassword-cli</module> <module>masterpassword-cli</module>
<module>masterpassword-android</module> <!--module>masterpassword-android</module-->
</modules> </modules>
<!-- REMOTE ARTIFACT REPOSITORIES --> <!-- REMOTE ARTIFACT REPOSITORIES -->

View File

@@ -37,6 +37,8 @@
- (NSString *)shortNameOfType:(MPElementType)type; - (NSString *)shortNameOfType:(MPElementType)type;
- (NSString *)classNameOfType:(MPElementType)type; - (NSString *)classNameOfType:(MPElementType)type;
- (Class)classOfType:(MPElementType)type; - (Class)classOfType:(MPElementType)type;
- (MPElementType)nextType:(MPElementType)type;
- (MPElementType)previousType:(MPElementType)type;
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key; - (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key;
- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key; - (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;

View File

@@ -31,6 +31,21 @@
return 0; return 0;
} }
- (NSString *)description {
return strf( @"<%@: version=%d>", NSStringFromClass( [self class] ), self.version );
}
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other conformsToProtocol:@protocol(MPAlgorithm)])
return NO;
return [(id<MPAlgorithm>)other version] == [self version];
}
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc { - (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
NSError *error = nil; NSError *error = nil;
@@ -127,7 +142,7 @@
return @"Device Private Password"; return @"Device Private Password";
} }
Throw(@"Type not supported: %d", type); Throw(@"Type not supported: %lu", (long)type);
} }
- (NSString *)shortNameOfType:(MPElementType)type { - (NSString *)shortNameOfType:(MPElementType)type {
@@ -161,7 +176,7 @@
return @"Device"; return @"Device";
} }
Throw(@"Type not supported: %d", type); Throw(@"Type not supported: %lu", (long)type);
} }
- (NSString *)classNameOfType:(MPElementType)type { - (NSString *)classNameOfType:(MPElementType)type {
@@ -200,7 +215,43 @@
return [MPElementStoredEntity class]; return [MPElementStoredEntity class];
} }
Throw(@"Type not supported: %d", type); Throw(@"Type not supported: %lu", (long)type);
}
- (MPElementType)nextType:(MPElementType)type {
if (!type)
Throw(@"No type given.");
switch (type) {
case MPElementTypeGeneratedMaximum:
return MPElementTypeStoredDevicePrivate;
case MPElementTypeGeneratedLong:
return MPElementTypeGeneratedMaximum;
case MPElementTypeGeneratedMedium:
return MPElementTypeGeneratedLong;
case MPElementTypeGeneratedBasic:
return MPElementTypeGeneratedMedium;
case MPElementTypeGeneratedShort:
return MPElementTypeGeneratedBasic;
case MPElementTypeGeneratedPIN:
return MPElementTypeGeneratedShort;
case MPElementTypeStoredPersonal:
return MPElementTypeGeneratedPIN;
case MPElementTypeStoredDevicePrivate:
return MPElementTypeStoredPersonal;
}
Throw(@"Type not supported: %lu", (long)type);
}
- (MPElementType)previousType:(MPElementType)type {
MPElementType previousType = type, nextType = type;
while ((nextType = [self nextType:nextType]) != type)
previousType = nextType;
return previousType;
} }
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key { - (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
@@ -214,23 +265,24 @@
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length); uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)]; NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)]; NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)];
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], name, [counterBytes encodeHex]); trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64],
[nameLengthBytes encodeHex], name, [counterBytes encodeHex]);
NSData *seed = [[NSData dataByConcatenatingDatas: NSData *seed = [[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
nameLengthBytes, nameLengthBytes, [name dataUsingEncoding:NSUTF8StringEncoding],
[name dataUsingEncoding:NSUTF8StringEncoding], counterBytes, nil]
counterBytes,
nil]
hmacWith:PearlHashSHA256 key:key.keyData]; hmacWith:PearlHashSHA256 key:key.keyData];
trc(@"seed is: %@", [seed encodeBase64]); trc(@"seed is: %@", [seed encodeBase64]);
const char *seedBytes = seed.bytes; const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte. // Determine the cipher from the first seed byte.
NSAssert([seed length], @"Missing seed."); NSAssert([seed length], @"Missing seed.");
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] NSString *typeClass = [self classNameOfType:type];
valueForKey:[self nameOfType:type]]; NSString *typeName = [self nameOfType:type];
id classCiphers = [MPTypes_ciphers valueForKey:typeClass];
NSArray *typeCiphers = [classCiphers valueForKey:typeName];
NSString *cipher = typeCiphers[htons(seedBytes[0]) % [typeCiphers count]]; NSString *cipher = typeCiphers[htons(seedBytes[0]) % [typeCiphers count]];
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher); trc(@"type %@, ciphers: %@, selected: %@", typeName, typeCiphers, cipher);
// Encode the content, character by character, using subsequent seed bytes and the cipher. // Encode the content, character by character, using subsequent seed bytes and the cipher.
NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher."); NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher.");
@@ -239,8 +291,7 @@
uint16_t keyByte = htons(seedBytes[c + 1]); uint16_t keyByte = htons(seedBytes[c + 1]);
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )]; NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass]; NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
1 )];
trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character); trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
[content appendString:character]; [content appendString:character];
@@ -264,13 +315,13 @@
case MPElementTypeGeneratedBasic: case MPElementTypeGeneratedBasic:
case MPElementTypeGeneratedShort: case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN: { case MPElementTypeGeneratedPIN: {
NSAssert(NO, @"Cannot save content to element with generated type %d.", element.type); NSAssert(NO, @"Cannot save content to element with generated type %lu.", (long)element.type);
break; break;
} }
case MPElementTypeStoredPersonal: { case MPElementTypeStoredPersonal: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]], NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
@@ -279,7 +330,7 @@
} }
case MPElementTypeStoredDevicePrivate: { case MPElementTypeStoredDevicePrivate: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]], NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
@@ -324,7 +375,7 @@
case MPElementTypeGeneratedShort: case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN: { case MPElementTypeGeneratedPIN: {
NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]], NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]],
@"Element with generated type %d is not an MPElementGeneratedEntity, but a %@.", element.type, [element class]); @"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.", (long)element.type, [element class]);
NSString *name = element.name; NSString *name = element.name;
MPElementType type = element.type; MPElementType type = element.type;
@@ -346,7 +397,7 @@
case MPElementTypeStoredPersonal: { case MPElementTypeStoredPersonal: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]], NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject; NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
@@ -358,7 +409,7 @@
} }
case MPElementTypeStoredDevicePrivate: { case MPElementTypeStoredDevicePrivate: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]], NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name]; NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery]; NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
@@ -387,7 +438,7 @@
case MPElementTypeStoredPersonal: { case MPElementTypeStoredPersonal: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]], NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
if ([importKey.keyID isEqualToData:elementKey.keyID]) if ([importKey.keyID isEqualToData:elementKey.keyID])
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64]; ((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
@@ -445,7 +496,7 @@
case MPElementTypeStoredPersonal: { case MPElementTypeStoredPersonal: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]], NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
result = [((MPElementStoredEntity *)element).contentObject encodeBase64]; result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
break; break;
} }

View File

@@ -75,6 +75,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if (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.");
if (!user)
return NO;
MPKey *tryKey = nil; MPKey *tryKey = nil;
@@ -166,7 +168,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
MPKey *recoverKey = newKey; MPKey *recoverKey = newKey;
#ifdef PEARL_UIKIT #ifdef PEARL_UIKIT
PearlOverlay *activityOverlay = [PearlOverlay showOverlayWithTitle:PearlString( @"Migrating %ld sites...", (long)[user.elements count] )]; PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...", (long)[user.elements count] )];
#endif #endif
for (MPElementEntity *element in user.elements) { for (MPElementEntity *element in user.elements) {

View File

@@ -16,6 +16,7 @@
#endif #endif
@property(strong, nonatomic) MPKey *key; @property(strong, nonatomic) MPKey *key;
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
+ (instancetype)get; + (instancetype)get;

View File

@@ -10,9 +10,7 @@
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
@implementation MPAppDelegate_Shared { @implementation MPAppDelegate_Shared
NSManagedObjectID *_activeUserOID;
}
+ (MPAppDelegate_Shared *)get { + (MPAppDelegate_Shared *)get {
@@ -32,11 +30,12 @@
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc { - (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc {
if (!_activeUserOID || !moc) NSManagedObjectID *activeUserOID = self.activeUserOID;
if (!activeUserOID || !moc)
return nil; return nil;
NSError *error; NSError *error;
MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:_activeUserOID error:&error]; MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:activeUserOID error:&error];
if (!activeUser) { if (!activeUser) {
[self signOutAnimated:YES]; [self signOutAnimated:YES];
err(@"Failed to retrieve active user: %@", error); err(@"Failed to retrieve active user: %@", error);
@@ -51,7 +50,7 @@
if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error]) if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error])
err(@"Failed to obtain a permanent object ID after setting active user: %@", error); err(@"Failed to obtain a permanent object ID after setting active user: %@", error);
_activeUserOID = activeUser.objectID; self.activeUserOID = activeUser.objectID;
} }
@end @end

View File

@@ -21,6 +21,8 @@ typedef enum {
@interface MPAppDelegate_Shared(Store)<UbiquityStoreManagerDelegate> @interface MPAppDelegate_Shared(Store)<UbiquityStoreManagerDelegate>
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady; + (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
+ (BOOL)managedObjectContextForMainThreadPerformBlock:(void (^)(NSManagedObjectContext *mainContext))mocBlock;
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void (^)(NSManagedObjectContext *mainContext))mocBlock;
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock; + (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock;
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock; + (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock;
@@ -28,10 +30,10 @@ typedef enum {
/** @param completion The block to execute after adding the element, executed from the main thread with the new element in the main MOC. */ /** @param completion The block to execute after adding the element, executed from the main thread with the new element in the main MOC. */
- (void)addElementNamed:(NSString *)siteName completion:(void (^)(MPElementEntity *element))completion; - (void)addElementNamed:(NSString *)siteName completion:(void (^)(MPElementEntity *element))completion;
- (MPElementEntity *)changeElement:(MPElementEntity *)element inContext:(NSManagedObjectContext *)context toType:(MPElementType)type; - (MPElementEntity *)changeElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context toType:(MPElementType)type;
- (MPImportResult)importSites:(NSString *)importedSitesString - (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *(^)(NSString *userName))importPassword askImportPassword:(NSString *(^)(NSString *userName))importPassword
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword; askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords; - (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords;
@end @end

View File

@@ -32,7 +32,8 @@ typedef NS_ENUM(NSInteger, MPMigrationLevelCloudStore) {
}; };
@implementation MPAppDelegate_Shared(Store) @implementation MPAppDelegate_Shared(Store)
PearlAssociatedObjectProperty(NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext); PearlAssociatedObjectProperty(id, SaveObserver, saveObserver);
PearlAssociatedObjectProperty(NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext);
PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext); PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext);
@@ -48,14 +49,40 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
return mainManagedObjectContext; return mainManagedObjectContext;
} }
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock { + (BOOL)managedObjectContextForMainThreadPerformBlock:(void (^)(NSManagedObjectContext *mainContext))mocBlock {
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext) if (!mainManagedObjectContext)
return NO; return NO;
[mainManagedObjectContext performBlock:^{
mocBlock( mainManagedObjectContext );
}];
return YES;
}
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void (^)(NSManagedObjectContext *mainContext))mocBlock {
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext)
return NO;
[mainManagedObjectContext performBlockAndWait:^{
mocBlock( mainManagedObjectContext );
}];
return YES;
}
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock {
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
if (!privateManagedObjectContextIfReady)
return NO;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = mainManagedObjectContext; moc.parentContext = privateManagedObjectContextIfReady;
[moc performBlock:^{ [moc performBlock:^{
mocBlock( moc ); mocBlock( moc );
}]; }];
@@ -65,12 +92,12 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock { + (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock {
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
if (!mainManagedObjectContext) if (!privateManagedObjectContextIfReady)
return NO; return NO;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = mainManagedObjectContext; moc.parentContext = privateManagedObjectContextIfReady;
[moc performBlockAndWait:^{ [moc performBlockAndWait:^{
mocBlock( moc ); mocBlock( moc );
}]; }];
@@ -98,7 +125,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
storeManager = [[UbiquityStoreManager alloc] initStoreNamed:nil withManagedObjectModel:nil localStoreURL:nil storeManager = [[UbiquityStoreManager alloc] initStoreNamed:nil withManagedObjectModel:nil localStoreURL:nil
containerIdentifier:MPCloudContainerIdentifier containerIdentifier:MPCloudContainerIdentifier
additionalStoreOptions:@{ STORE_OPTIONS } storeConfiguration:nil storeOptions:@{ STORE_OPTIONS }
delegate:self]; delegate:self];
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
@@ -197,7 +224,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES] URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES]
URLByAppendingPathComponent:uuid isDirectory:NO] URLByAppendingPathExtension:@"sqlite"]; URLByAppendingPathComponent:uuid isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
return [self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL contentName:uuid]; return [self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL];
} }
- (BOOL)migrateV2CloudStore { - (BOOL)migrateV2CloudStore {
@@ -218,7 +245,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
URLByAppendingPathComponent:@"CloudStore.nosync" isDirectory:YES] URLByAppendingPathComponent:@"CloudStore.nosync" isDirectory:YES]
URLByAppendingPathComponent:uuid isDirectory:NO] URLByAppendingPathExtension:@"sqlite"]; URLByAppendingPathComponent:uuid isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
return [self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL contentName:uuid]; return [self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL];
} }
- (BOOL)migrateV1LocalStore { - (BOOL)migrateV1LocalStore {
@@ -255,7 +282,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
return YES; return YES;
} }
- (BOOL)migrateFromCloudStore:(NSURL *)oldCloudStoreURL cloudContent:(NSURL *)oldCloudContentURL contentName:(NSString *)contentName { - (BOOL)migrateFromCloudStore:(NSURL *)oldCloudStoreURL cloudContent:(NSURL *)oldCloudContentURL {
if (![self.storeManager cloudSafeForSeeding]) { if (![self.storeManager cloudSafeForSeeding]) {
inf(@"Can't migrate cloud store: A new cloud store already exists."); inf(@"Can't migrate cloud store: A new cloud store already exists.");
@@ -274,7 +301,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
#pragma mark - UbiquityStoreManagerDelegate #pragma mark - UbiquityStoreManagerDelegate
- (NSManagedObjectContext *)managedObjectContextForUbiquityChangesInManager:(UbiquityStoreManager *)manager { - (NSManagedObjectContext *)ubiquityStoreManager:(UbiquityStoreManager *)manager
managedObjectContextForUbiquityChanges:(NSNotification *)note {
return [self mainManagedObjectContextIfReady]; return [self mainManagedObjectContextIfReady];
} }
@@ -291,6 +319,11 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
[moc saveToStore]; [moc saveToStore];
[moc reset]; [moc reset];
if (self.saveObserver) {
[[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver];
self.saveObserver = nil;
}
self.privateManagedObjectContext = nil; self.privateManagedObjectContext = nil;
self.mainManagedObjectContext = nil; self.mainManagedObjectContext = nil;
}]; }];
@@ -307,8 +340,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
} ); } );
// Create our contexts. // Create our contexts.
NSManagedObjectContext NSManagedObjectContext *privateManagedObjectContext =
*privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateManagedObjectContext performBlockAndWait:^{ [privateManagedObjectContext performBlockAndWait:^{
privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
privateManagedObjectContext.persistentStoreCoordinator = coordinator; privateManagedObjectContext.persistentStoreCoordinator = coordinator;
@@ -331,6 +364,17 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
NSManagedObjectContext *mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; NSManagedObjectContext *mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
mainManagedObjectContext.parentContext = privateManagedObjectContext; mainManagedObjectContext.parentContext = privateManagedObjectContext;
if (self.saveObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver];
self.saveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification
object:privateManagedObjectContext queue:nil usingBlock:
^(NSNotification *note) {
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}];
}];
self.privateManagedObjectContext = privateManagedObjectContext; self.privateManagedObjectContext = privateManagedObjectContext;
self.mainManagedObjectContext = mainManagedObjectContext; self.mainManagedObjectContext = mainManagedObjectContext;
} }
@@ -359,46 +403,45 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [self activeUserInContext:context]; MPUserEntity *activeUser = [self activeUserInContext:context];
NSAssert(activeUser, @"Missing user."); NSAssert(activeUser, @"Missing user.");
if (!activeUser) if (!activeUser) {
completion( nil );
return; return;
}
MPElementType type = activeUser.defaultType; MPElementType type = activeUser.defaultType;
if (!type) NSString *typeEntityName = [MPAlgorithmDefault classNameOfType:type];
type = activeUser.defaultType = MPElementTypeGeneratedLong;
NSString *typeEntityClassName = [MPAlgorithmDefault classNameOfType:type];
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityClassName
inManagedObjectContext:context];
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
element.name = siteName; element.name = siteName;
element.user = activeUser; element.user = activeUser;
element.type = type; element.type = type;
element.lastUsed = [NSDate date]; element.lastUsed = [NSDate date];
element.version = MPAlgorithmDefaultVersion; element.version = MPAlgorithmDefaultVersion;
[context saveToStore];
NSError *error = nil; NSError *error = nil;
if (element.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ element ] error:&error]) if (element.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ element ] error:&error])
err(@"Failed to obtain a permanent object ID after creating new element: %@", error); err(@"Failed to obtain a permanent object ID after creating new element: %@", error);
NSManagedObjectID *elementOID = [element objectID]; [context saveToStore];
dispatch_async( dispatch_get_main_queue(), ^{
completion( completion( element );
(MPElementEntity *)[[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady] objectRegisteredForID:elementOID] );
} );
}]; }];
} }
- (MPElementEntity *)changeElement:(MPElementEntity *)element inContext:(NSManagedObjectContext *)context toType:(MPElementType)type { - (MPElementEntity *)changeElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context toType:(MPElementType)type {
if ([element.algorithm classOfType:type] == element.typeClass) if (element.type == type)
return element;
if ([element.algorithm classOfType:type] == element.typeClass) {
element.type = type; element.type = type;
[context saveToStore];
}
else { else {
// Type requires a different class of element. Recreate the element. // Type requires a different class of element. Recreate the element.
MPElementEntity *newElement NSString *typeEntityName = [element.algorithm classNameOfType:type];
= [NSEntityDescription insertNewObjectForEntityForName:[element.algorithm classNameOfType:type] MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
inManagedObjectContext:context];
newElement.type = type; newElement.type = type;
newElement.name = element.name; newElement.name = element.name;
newElement.user = element.user; newElement.user = element.user;
@@ -407,13 +450,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
newElement.version = element.version; newElement.version = element.version;
newElement.loginName = element.loginName; newElement.loginName = element.loginName;
[context deleteObject:element]; NSError *error = nil;
[context saveToStore];
NSError *error;
if (![context obtainPermanentIDsForObjects:@[ newElement ] error:&error]) if (![context obtainPermanentIDsForObjects:@[ newElement ] error:&error])
err(@"Failed to obtain a permanent object ID after changing object type: %@", error); err(@"Failed to obtain a permanent object ID after changing object type: %@", error);
[context deleteObject:element];
[context saveToStore];
[[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:element.objectID];
element = newElement; element = newElement;
} }
@@ -578,7 +622,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
// Ask for confirmation to import these sites and the master password of the user. // Ask for confirmation to import these sites and the master password of the user.
inf(@"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count], (unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName]); inf(@"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count], (unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName]);
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count], [elementsToDelete count] ); NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count],
[elementsToDelete count] );
if (!userMasterPassword) { if (!userMasterPassword) {
inf(@"Import cancelled."); inf(@"Import cancelled.");
return MPImportResultCancelled; return MPImportResultCancelled;
@@ -602,8 +647,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
// Make sure there is a user. // Make sure there is a user.
if (!user) { if (!user) {
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] ) user = [MPUserEntity insertNewObjectInContext:context];
inManagedObjectContext:context];
user.name = importUserName; user.name = importUserName;
user.keyID = importKeyID; user.keyID = importKeyID;
dbg(@"Created User: %@", [user debugDescription]); dbg(@"Created User: %@", [user debugDescription]);
@@ -619,9 +663,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
NSString *exportContent = siteElements[5]; NSString *exportContent = siteElements[5];
// Create new site. // Create new site.
MPElementEntity NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
*element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion( version ) classNameOfType:type] MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
inManagedObjectContext:context];
element.name = name; element.name = name;
element.user = user; element.user = user;
element.type = type; element.type = type;
@@ -645,21 +688,21 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
MPCheckpoint( MPCheckpointSitesImported, nil ); MPCheckpoint( MPCheckpointSitesImported, nil );
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{ [[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
MPSitesImportedNotificationUserKey: user MPSitesImportedNotificationUserKey : user
}]; }];
return MPImportResultSuccess; return MPImportResultSuccess;
} }
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords { - (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
MPUserEntity *activeUser = [self activeUserForMainThread]; MPUserEntity *activeUser = [self activeUserForMainThread];
inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", activeUser.userID); inf(@"Exporting sites, %@, for: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID);
// Header. // Header.
NSMutableString *export = [NSMutableString new]; NSMutableString *export = [NSMutableString new];
[export appendFormat:@"# Master Password site export\n"]; [export appendFormat:@"# Master Password site export\n"];
if (showPasswords) if (revealPasswords)
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"]; [export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
else else
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"]; [export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
@@ -669,7 +712,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
[export appendFormat:@"# User Name: %@\n", activeUser.name]; [export appendFormat:@"# User Name: %@\n", activeUser.name];
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]]; [export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]]; [export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
if (showPasswords) if (revealPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"]; [export appendFormat:@"# Passwords: VISIBLE\n"];
else else
[export appendFormat:@"# Passwords: PROTECTED\n"]; [export appendFormat:@"# Passwords: PROTECTED\n"];
@@ -689,7 +732,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
// Determine the content to export. // Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) { if (!(type & MPElementFeatureDevicePrivate)) {
if (showPasswords) if (revealPasswords)
content = [element.algorithm resolveContentForElement:element usingKey:self.key]; content = [element.algorithm resolveContentForElement:element usingKey:self.key];
else if (type & MPElementFeatureExportContent) else if (type & MPElementFeatureExportContent)
content = [element.algorithm exportContentForElement:element usingKey:self.key]; content = [element.algorithm exportContentForElement:element usingKey:self.key];
@@ -697,12 +740,12 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n", [export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses, [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
[PearlString( @"%u:%lu", type, (unsigned long)version ) UTF8String], [name UTF8String], content [PearlString( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content
? content: @""]; ? content: @""];
} }
MPCheckpoint( MPCheckpointSitesExported, @{ MPCheckpoint( MPCheckpointSitesExported, @{
@"showPasswords" : @(showPasswords) @"showPasswords" : @(revealPasswords)
} ); } );
return export; return export;

View File

@@ -35,6 +35,8 @@
- (NSUInteger)use; - (NSUInteger)use;
- (BOOL)migrateExplicitly:(BOOL)explicit; - (BOOL)migrateExplicitly:(BOOL)explicit;
- (NSString *)resolveContentUsingKey:(MPKey *)key;
- (void)resolveContentUsingKey:(MPKey *)key result:(void (^)(NSString *))result;
@end @end

View File

@@ -8,13 +8,14 @@
#import "MPEntities.h" #import "MPEntities.h"
#import "MPAppDelegate_Shared.h" #import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
@implementation NSManagedObjectContext(MP) @implementation NSManagedObjectContext(MP)
- (BOOL)saveToStore { - (BOOL)saveToStore {
__block BOOL success = YES; __block BOOL success = YES;
if ([self hasChanges]) if ([self hasChanges]) {
[self performBlockAndWait:^{ [self performBlockAndWait:^{
@try { @try {
NSError *error = nil; NSError *error = nil;
@@ -26,6 +27,7 @@
err(@"While saving: %@", exception); err(@"While saving: %@", exception);
} }
}]; }];
}
return success && (!self.parentContext || [self.parentContext saveToStore]); return success && (!self.parentContext || [self.parentContext saveToStore]);
} }
@@ -42,6 +44,14 @@
type = [self.user defaultType]; type = [self.user defaultType];
if (!type || type == (MPElementType)NSNotFound) if (!type || type == (MPElementType)NSNotFound)
type = MPElementTypeGeneratedLong; type = MPElementTypeGeneratedLong;
if (![self isKindOfClass:[self.algorithm classOfType:type]]) {
// NSAssert(NO, @"This object's class does not support the type: %lu", (long)type);
for (MPElementType aType = type; type != (aType = [self.algorithm nextType:aType]);)
if ([self isKindOfClass:[self.algorithm classOfType:aType]]) {
err(@"Invalid type for: %@, type: %lu. Will use %lu instead.", self.name, (long)type, (long)aType);
return aType;
}
}
return type; return type;
} }
@@ -53,6 +63,8 @@
aType = [self.user defaultType]; aType = [self.user defaultType];
if (!aType || aType == (MPElementType)NSNotFound) if (!aType || aType == (MPElementType)NSNotFound)
aType = MPElementTypeGeneratedLong; aType = MPElementTypeGeneratedLong;
if (![self isKindOfClass:[self.algorithm classOfType:aType]])
Throw(@"This object's class does not support the type: %lu", (long)aType);
self.type_ = @(aType); self.type_ = @(aType);
} }
@@ -125,8 +137,8 @@
- (NSString *)debugDescription { - (NSString *)debugDescription {
return PearlString( @"{%@: name=%@, user=%@, type=%d, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}", return PearlString( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, self.type, (long)self.uses, self.lastUsed, (long)self.version, NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed, (long)self.version,
self.loginName, self.requiresExplicitMigration ); self.loginName, self.requiresExplicitMigration );
} }
@@ -143,6 +155,16 @@
return YES; return YES;
} }
- (NSString *)resolveContentUsingKey:(MPKey *)key {
return [self.algorithm resolveContentForElement:self usingKey:key];
}
- (void)resolveContentUsingKey:(MPKey *)key result:(void (^)(NSString *))result {
[self.algorithm resolveContentForElement:self usingKey:key result:result];
}
@end @end
@implementation MPElementGeneratedEntity(MP) @implementation MPElementGeneratedEntity(MP)
@@ -187,7 +209,7 @@
- (MPElementType)defaultType { - (MPElementType)defaultType {
return (MPElementType)[self.defaultType_ unsignedIntegerValue]; return IfElse((MPElementType)[self.defaultType_ unsignedIntegerValue], MPElementTypeGeneratedLong);
} }
- (void)setDefaultType:(MPElementType)aDefaultType { - (void)setDefaultType:(MPElementType)aDefaultType {

View File

@@ -8,27 +8,27 @@
#import "MPKey.h" #import "MPKey.h"
typedef enum { typedef NS_ENUM(NSUInteger, MPElementContentType) {
MPElementContentTypePassword, MPElementContentTypePassword,
MPElementContentTypeNote, MPElementContentTypeNote,
MPElementContentTypePicture, MPElementContentTypePicture,
} MPElementContentType; };
typedef enum { typedef NS_ENUM(NSUInteger, MPElementTypeClass) {
/** Generate the password. */ /** Generate the password. */
MPElementTypeClassGenerated = 1 << 4, MPElementTypeClassGenerated = 1 << 4,
/** Store the password. */ /** Store the password. */
MPElementTypeClassStored = 1 << 5, MPElementTypeClassStored = 1 << 5,
} MPElementTypeClass; };
typedef enum { typedef NS_ENUM(NSUInteger, MPElementFeature) {
/** Export the key-protected content data. */ /** Export the key-protected content data. */
MPElementFeatureExportContent = 1 << 10, MPElementFeatureExportContent = 1 << 10,
/** Never export content. */ /** Never export content. */
MPElementFeatureDevicePrivate = 1 << 11, MPElementFeatureDevicePrivate = 1 << 11,
} MPElementFeature; };
typedef enum { typedef NS_ENUM(NSUInteger, MPElementType) {
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0, MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
@@ -38,7 +38,7 @@ typedef enum {
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent, MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate, MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
} MPElementType; };
#define MPErrorDomain @"MPErrorDomain" #define MPErrorDomain @"MPErrorDomain"

View File

@@ -0,0 +1,33 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPElementCollectionView.h
// MPElementCollectionView
//
// Created by lhunath on 2/11/2014.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@class MPElementModel;
@interface MPElementCollectionView : NSCollectionViewItem
@property (nonatomic) MPElementModel *representedObject;
@property (nonatomic) BOOL counterHidden;
@property (nonatomic) BOOL updateContentHidden;
- (IBAction)toggleType:(id)sender;
- (IBAction)updateLoginName:(id)sender;
- (IBAction)updateContent:(id)sender;
- (IBAction)delete:(id)sender;
@end

View File

@@ -0,0 +1,225 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPElementCollectionView.h
// MPElementCollectionView
//
// Created by lhunath on 2/11/2014.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPElementCollectionView.h"
#import "MPElementModel.h"
#import "MPMacAppDelegate.h"
#import "MPAppDelegate_Store.h"
#define MPAlertChangeType @"MPAlertChangeType"
#define MPAlertChangeLogin @"MPAlertChangeLogin"
#define MPAlertChangeContent @"MPAlertChangeContent"
#define MPAlertDeleteSite @"MPAlertDeleteSite"
@implementation MPElementCollectionView {
}
@dynamic representedObject;
- (id)initWithCoder:(NSCoder *)coder {
if (!(self = [super initWithCoder:coder]))
return nil;
[self addObserver:self forKeyPath:@"representedObject" options:0 context:nil];
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.counterHidden = !(MPElementTypeClassGenerated & self.representedObject.type);
self.updateContentHidden = !(MPElementTypeClassStored & self.representedObject.type);
}];
}
- (void)dealloc {
[self removeObserver:self forKeyPath:@"representedObject"];
}
- (IBAction)toggleType:(id)sender {
id<MPAlgorithm> algorithm = self.representedObject.algorithm;
NSString *previousType = [algorithm nameOfType:[algorithm previousType:self.representedObject.type]];
NSString *nextType = [algorithm nameOfType:[algorithm nextType:self.representedObject.type]];
[[NSAlert alertWithMessageText:@"Change Password Type"
defaultButton:nextType alternateButton:@"Cancel" otherButton:previousType
informativeTextWithFormat:@"Changing the password type for this site will cause the password to change.\n"
@"You will need to update your account with the new password.\n\n"
@"Changing back to the old type will restore your current password."]
beginSheetModalForWindow:self.view.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeType];
}
- (IBAction)updateLoginName:(id)sender {
NSAlert *alert = [NSAlert alertWithMessageText:@"Update Login Name"
defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil
informativeTextWithFormat:@"Enter the login name for %@:", self.representedObject.site];
NSTextField *passwordField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:passwordField];
[alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.view.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeLogin];
}
- (IBAction)updateContent:(id)sender {
NSAlert *alert = [NSAlert alertWithMessageText:@"Update Password"
defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil
informativeTextWithFormat:@"Enter the new password for %@:", self.representedObject.site];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:passwordField];
[alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.view.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeContent];
}
- (IBAction)delete:(id)sender {
NSAlert *alert = [NSAlert alertWithMessageText:@"Delete Site"
defaultButton:@"Delete" alternateButton:@"Cancel" otherButton:nil
informativeTextWithFormat:@"Are you sure you want to delete the site: %@?", self.representedObject.site];
[alert beginSheetModalForWindow:self.view.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertDeleteSite];
}
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
if (contextInfo == MPAlertChangeType) {
switch (returnCode) {
case NSAlertDefaultReturn: {
// "Next type" button.
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self.representedObject entityInContext:context];
element = [[MPMacAppDelegate get] changeElement:element saveInContext:context
toType:[element.algorithm nextType:element.type]];
self.representedObject = [[MPElementModel alloc] initWithEntity:element];
}];
break;
}
case NSAlertAlternateReturn: {
// "Cancel" button.
break;
}
case NSAlertOtherReturn: {
// "Previous type" button.
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self.representedObject entityInContext:context];
element = [[MPMacAppDelegate get] changeElement:element saveInContext:context
toType:[element.algorithm previousType:element.type]];
self.representedObject = [[MPElementModel alloc] initWithEntity:element];
}];
break;
}
default:
break;
}
return;
}
if (contextInfo == MPAlertChangeLogin) {
switch (returnCode) {
case NSAlertDefaultReturn: {
// "Update" button.
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self.representedObject entityInContext:context];
element.loginName = [(NSTextField *)alert.accessoryView stringValue];
[context saveToStore];
self.representedObject = [[MPElementModel alloc] initWithEntity:element];
}];
break;
}
case NSAlertAlternateReturn: {
// "Cancel" button.
break;
}
default:
break;
}
return;
}
if (contextInfo == MPAlertChangeContent) {
switch (returnCode) {
case NSAlertDefaultReturn: {
// "Update" button.
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self.representedObject entityInContext:context];
[element.algorithm saveContent:[(NSSecureTextField *)alert.accessoryView stringValue]
toElement:element usingKey:[MPMacAppDelegate get].key];
[context saveToStore];
self.representedObject = [[MPElementModel alloc] initWithEntity:element];
}];
break;
}
case NSAlertAlternateReturn: {
// "Cancel" button.
break;
}
default:
break;
}
return;
}
if (contextInfo == MPAlertDeleteSite) {
switch (returnCode) {
case NSAlertDefaultReturn: {
// "Delete" button.
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self.representedObject entityInContext:context];
[context deleteObject:element];
[context saveToStore];
[((MPPasswordWindowController *)self.collectionView.window.windowController) updateElements];
}];
break;
}
case NSAlertAlternateReturn: {
// "Cancel" button.
break;
}
default:
break;
}
return;
}
}
@end

View File

@@ -0,0 +1,38 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPElementModel.h
// MPElementModel
//
// Created by lhunath on 2/11/2014.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@class MPElementEntity;
@interface MPElementModel : NSObject
@property (nonatomic, readonly) NSString *site;
@property (nonatomic, readonly) MPElementType type;
@property (nonatomic, readonly) NSString *typeName;
@property (nonatomic, readonly) NSString *content;
@property (nonatomic, readonly) NSString *loginName;
@property (nonatomic, readonly) NSNumber *uses;
@property (nonatomic) NSUInteger counter;
@property (nonatomic, readonly) NSDate *lastUsed;
@property (nonatomic, readonly) id<MPAlgorithm> algorithm;
@property (nonatomic, readonly) NSArray *types;
@property (nonatomic) NSUInteger typeIndex;
- (id)initWithEntity:(MPElementEntity *)entity;
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc;
@end

View File

@@ -0,0 +1,113 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPElementModel.h
// MPElementModel
//
// Created by lhunath on 2/11/2014.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPElementModel.h"
#import "MPElementEntity.h"
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
#import "MPMacAppDelegate.h"
@interface MPElementModel()
@property(nonatomic, strong) NSManagedObjectID *entityOID;
@property(nonatomic, readwrite) NSString *content;
@property(nonatomic, readwrite) MPElementType type;
@property(nonatomic, readwrite) NSString *typeName;
@end
@implementation MPElementModel {
NSMutableDictionary *_typesByName;
}
- (id)initWithEntity:(MPElementEntity *)entity {
if (!(self = [super init]))
return nil;
_site = entity.name;
_lastUsed = entity.lastUsed;
_loginName = entity.loginName;
_type = entity.type;
_typeName = entity.typeName;
_uses = entity.uses_;
_counter = [entity isKindOfClass:[MPElementGeneratedEntity class]]? [(MPElementGeneratedEntity *)entity counter]: 0;
_content = [entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key];
_algorithm = entity.algorithm;
_entityOID = entity.objectID;
// Find all password types and the index of the current type amongst them.
_typesByName = [NSMutableDictionary dictionary];
MPElementType type = _type;
do {
[_typesByName setObject:@(type) forKey:[_algorithm shortNameOfType:type]];
} while (_type != (type = [_algorithm nextType:type]));
_types = [_typesByName keysSortedByValueUsingSelector:@selector(compare:)];
_typeIndex = [[[_typesByName allValues] sortedArrayUsingSelector:@selector(compare:)] indexOfObject:@(_type)];
return self;
}
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc {
if (!_entityOID)
return nil;
NSError *error;
MPElementEntity *entity = (MPElementEntity *)[moc existingObjectWithID:_entityOID error:&error];
if (!entity)
err(@"Couldn't retrieve active element: %@", error);
return entity;
}
- (void)setCounter:(NSUInteger)counter {
if (counter == _counter)
return;
_counter = counter;
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *entity = [self entityInContext:context];
if ([entity isKindOfClass:[MPElementGeneratedEntity class]]) {
((MPElementGeneratedEntity *)entity).counter = counter;
[context saveToStore];
self.content = [entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key];
}
}];
}
- (void)setTypeIndex:(NSUInteger)typeIndex {
if (typeIndex == _typeIndex)
return;
_typeIndex = typeIndex;
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *entity = [self entityInContext:context];
entity.type_ = _typesByName[_types[typeIndex]];
[context saveToStore];
self.type = entity.type;
self.typeName = entity.typeName;
self.content = [entity.algorithm resolveContentForElement:entity usingKey:[MPAppDelegate_Shared get].key];
}];
}
@end

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@
- (IBAction)newUser:(NSMenuItem *)sender; - (IBAction)newUser:(NSMenuItem *)sender;
- (IBAction)lock:(id)sender; - (IBAction)lock:(id)sender;
- (IBAction)rebuildCloud:(id)sender; - (IBAction)rebuildCloud:(id)sender;
- (IBAction)corruptCloud:(id)sender;
- (IBAction)terminate:(id)sender; - (IBAction)terminate:(id)sender;
- (IBAction)iphoneAppStore:(id)sender; - (IBAction)iphoneAppStore:(id)sender;

View File

@@ -14,6 +14,12 @@
#define LOGIN_HELPER_BUNDLE_ID @"com.lyndir.lhunath.MasterPassword.Mac.LoginHelper" #define LOGIN_HELPER_BUNDLE_ID @"com.lyndir.lhunath.MasterPassword.Mac.LoginHelper"
@interface UbiquityStoreManager (Private)
- (void)markCloudStoreCorrupted;
@end
@interface MPMacAppDelegate() @interface MPMacAppDelegate()
@property(nonatomic, strong) NSWindowController *initialWindow; @property(nonatomic, strong) NSWindowController *initialWindow;
@@ -129,16 +135,23 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)togglePreference:(id)sender { - (IBAction)togglePreference:(id)sender {
if (sender == self.enableCloudButton) if (sender == self.enableCloudButton) {
[self storeManager].cloudEnabled = (self.enableCloudButton.state == NSOnState); if (([self storeManager].cloudEnabled = self.enableCloudButton.state == NSOnState)) {
NSAlert *alert = [NSAlert new];
alert.messageText = @"iCloud Enabled";
alert.informativeText = @"If you already have a user on another iCloud-enabled device, "
@"it may take a moment for that user to sync down to this device.";
[alert runModal];
}
}
if (sender == self.useCloudItem) if (sender == self.useCloudItem)
[self storeManager].cloudEnabled = !(self.useCloudItem.state == NSOnState); [self storeManager].cloudEnabled = self.useCloudItem.state != NSOnState;
if (sender == self.rememberPasswordItem) if (sender == self.rememberPasswordItem)
[MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]]; [MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]];
if (sender == self.openAtLoginButton) if (sender == self.openAtLoginButton)
[self setLoginItemEnabled:(self.openAtLoginButton.state == NSOnState)]; [self setLoginItemEnabled:self.openAtLoginButton.state == NSOnState];
if (sender == self.openAtLoginItem) if (sender == self.openAtLoginItem)
[self setLoginItemEnabled:!(self.openAtLoginItem.state == NSOnState)]; [self setLoginItemEnabled:self.openAtLoginItem.state != NSOnState];
if (sender == self.savePasswordItem) { if (sender == self.savePasswordItem) {
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { [MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context]; MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
@@ -194,14 +207,24 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)rebuildCloud:(id)sender { - (IBAction)rebuildCloud:(id)sender {
if ([[NSAlert alertWithMessageText:@"iCloud Truth Sync" defaultButton:@"Continue" if ([[NSAlert alertWithMessageText:@"iCloud Truth Push" defaultButton:@"Continue"
alternateButton:nil otherButton:@"Cancel" alternateButton:nil otherButton:@"Cancel"
informativeTextWithFormat:@"This action will force all your iCloud enabled devices to revert to this device's version of the truth." informativeTextWithFormat:@"This action will force all your iCloud enabled devices to switch to this device's version of the truth."
@"\n\nThis is only necessary if you notice that your devices aren't syncing properly anymore. " @"\n\nThis is only necessary if you notice that your devices aren't syncing properly anymore. "
"Any data on other devices not available from here will be lost."] runModal] == NSAlertDefaultReturn) "Any data on other devices not available from here will be lost."] runModal] == NSAlertDefaultReturn)
[self.storeManager rebuildCloudContentFromCloudStoreOrLocalStore:NO]; [self.storeManager rebuildCloudContentFromCloudStoreOrLocalStore:NO];
} }
- (IBAction)corruptCloud:(id)sender {
if ([[NSAlert alertWithMessageText:@"iCloud Truth Pull" defaultButton:@"Continue"
alternateButton:nil otherButton:@"Cancel"
informativeTextWithFormat:@"This action will force another iCloud enabled device to push their version of the truth on all."
@"\n\nThis is only necessary if you notice that your devices aren't syncing properly anymore. "
"Any data on this device not available from the other will be lost."] runModal] == NSAlertDefaultReturn)
[self.storeManager markCloudStoreCorrupted];
}
- (IBAction)terminate:(id)sender { - (IBAction)terminate:(id)sender {
[self.passwordWindow close]; [self.passwordWindow close];
@@ -301,8 +324,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
// Initial display. // Initial display.
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
if ([[MPMacConfig get].firstRun boolValue]) if ([[MPMacConfig get].firstRun boolValue]) {
[self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self] showWindow:self]; self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self];
[self.initialWindow.window setLevel:NSFloatingWindowLevel];
[self.initialWindow showWindow:self];
}
} }
- (void)setActiveUser:(MPUserEntity *)activeUser { - (void)setActiveUser:(MPUserEntity *)activeUser {

View File

@@ -7,16 +7,17 @@
// //
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@class MPElementModel;
@interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate, NSComboBoxDelegate> @interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate, NSCollectionViewDelegate>
@property(nonatomic, weak) IBOutlet NSTextField *siteLabel; @property(nonatomic, strong) NSMutableArray *elements;
@property(nonatomic, strong) NSIndexSet *elementSelectionIndexes;
@property(nonatomic, weak) IBOutlet NSTextField *siteField; @property(nonatomic, weak) IBOutlet NSTextField *siteField;
@property(nonatomic, weak) IBOutlet NSTextField *contentField;
@property(nonatomic, weak) IBOutlet NSTextField *tipField;
@property(nonatomic, weak) IBOutlet NSComboBox *typeField;
@property(nonatomic, weak) IBOutlet NSView *contentContainer; @property(nonatomic, weak) IBOutlet NSView *contentContainer;
@property(nonatomic, weak) IBOutlet NSProgressIndicator *progressView;
@property(nonatomic, weak) IBOutlet NSTextField *userLabel; @property(nonatomic, weak) IBOutlet NSTextField *userLabel;
@property(nonatomic, weak) IBOutlet NSCollectionView *siteCollectionView;
- (void)updateElements;
@end @end

View File

@@ -10,44 +10,40 @@
#import "MPMacAppDelegate.h" #import "MPMacAppDelegate.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPElementModel.h"
#define MPAlertUnlockMP @"MPAlertUnlockMP" #define MPAlertUnlockMP @"MPAlertUnlockMP"
#define MPAlertIncorrectMP @"MPAlertIncorrectMP" #define MPAlertIncorrectMP @"MPAlertIncorrectMP"
#define MPAlertCreateSite @"MPAlertCreateSite" #define MPAlertCreateSite @"MPAlertCreateSite"
#define MPAlertChangeType @"MPAlertChangeType"
@interface MPPasswordWindowController() @interface MPPasswordWindowController()
@property(nonatomic) BOOL inProgress; @property(nonatomic) BOOL inProgress;
@property(nonatomic) BOOL siteFieldPreventCompletion;
@property(nonatomic, strong) NSOperationQueue *backgroundQueue; @property(nonatomic, strong) NSOperationQueue *backgroundQueue;
@property(nonatomic, strong) NSAlert *loadingDataAlert; @property(nonatomic, strong) NSAlert *loadingDataAlert;
@property(nonatomic) BOOL closing; @property(nonatomic) BOOL closing;
@end @end
@implementation MPPasswordWindowController { @implementation MPPasswordWindowController
NSManagedObjectID *_activeElementOID;
} #pragma mark - Life
- (void)windowDidLoad { - (void)windowDidLoad {
if ([[MPMacConfig get].dialogStyleHUD boolValue]) { if ([[MPMacConfig get].dialogStyleHUD boolValue]) {
self.window.styleMask = NSHUDWindowMask | NSTitledWindowMask | NSUtilityWindowMask | NSClosableWindowMask; self.window.styleMask = NSHUDWindowMask | NSTitledWindowMask | NSUtilityWindowMask | NSClosableWindowMask;
self.siteLabel.textColor = [NSColor whiteColor]; self.userLabel.textColor = [NSColor whiteColor];
} }
else { else {
self.window.styleMask = NSTexturedBackgroundWindowMask | NSResizableWindowMask | NSTitledWindowMask | NSClosableWindowMask; self.window.styleMask = NSTexturedBackgroundWindowMask | NSResizableWindowMask | NSTitledWindowMask | NSClosableWindowMask;
self.siteLabel.textColor = [NSColor controlTextColor]; self.userLabel.textColor = [NSColor controlTextColor];
} }
self.backgroundQueue = [NSOperationQueue new]; self.backgroundQueue = [NSOperationQueue new];
self.backgroundQueue.maxConcurrentOperationCount = 1; self.backgroundQueue.maxConcurrentOperationCount = 1;
[self setContent:@""];
[self.tipField setStringValue:@""];
[self.userLabel setStringValue:PearlString( @"%@'s password for:", [[MPMacAppDelegate get] activeUserForMainThread].name )];
[[MPMacAppDelegate get] addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { [[MPMacAppDelegate get] addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
// [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { // [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
// if (![MPAlgorithmDefault migrateUser:[[MPMacAppDelegate get] activeUserInContext:moc]]) // if (![MPAlgorithmDefault migrateUser:[[MPMacAppDelegate get] activeUserInContext:moc]])
@@ -57,40 +53,46 @@
// @"their passwords to change. You'll need to update your profile for that site with the new password."]; // @"their passwords to change. You'll need to update your profile for that site with the new password."];
// [moc saveToStore]; // [moc saveToStore];
// }]; // }];
dispatch_async(dispatch_get_main_queue(), ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES]; [self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES];
}); }];
} forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil]; } forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO]; [self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO];
[self.siteField selectText:nil]; [self.siteField selectText:nil];
}]; }];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
NSWindow *sheet = [self.window attachedSheet]; NSWindow *sheet = [self.window attachedSheet];
if (sheet) if (sheet)
[NSApp endSheet:sheet]; [NSApp endSheet:sheet];
[NSApp hide:nil]; [NSApp hide:nil];
self.closing = NO; self.closing = NO;
}]; }];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
_activeElementOID = nil; self.userLabel.stringValue = @"";
[self.siteField setStringValue:@""]; self.siteField.stringValue = @"";
[self.typeField deselectItemAtIndex:[self.typeField indexOfSelectedItem]]; self.elements = nil;
[self trySiteWithAction:NO];
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES]; [self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES];
}]; }];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
self.userLabel.stringValue = PearlString( @"%@'s password for:",
[[MPMacAppDelegate get] activeUserForMainThread].name );
}];
[[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil [[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO]; [self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO];
}]; }];
[super windowDidLoad]; [super windowDidLoad];
} }
@@ -101,6 +103,152 @@
[super close]; [super close];
} }
#pragma mark - NSAlert
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
if (contextInfo == MPAlertIncorrectMP) {
[self close];
return;
}
if (contextInfo == MPAlertUnlockMP) {
switch (returnCode) {
case NSAlertFirstButtonReturn: {
// "Unlock" button.
self.contentContainer.alphaValue = 0;
self.inProgress = YES;
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
NSString *userName = activeUser.name;
BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc
usingMasterPassword:password];
self.inProgress = NO;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (success)
self.contentContainer.alphaValue = 1;
else {
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
NSLocalizedDescriptionKey : PearlString( @"Incorrect master password for user %@", userName )
}]] beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertIncorrectMP];
}
}];
}];
break;
}
case NSAlertSecondButtonReturn: {
// "Change" button.
NSAlert *alert_ = [NSAlert new];
[alert_ addButtonWithTitle:@"Update"];
[alert_ addButtonWithTitle:@"Cancel"];
[alert_ setMessageText:@"Changing Master Password"];
[alert_ setInformativeText:@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."];
if ([alert_ runModal] == NSAlertFirstButtonReturn) {
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
activeUser.keyID = nil;
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[[MPMacAppDelegate get] signOutAnimated:YES];
[context saveToStore];
}];
}
break;
}
case NSAlertThirdButtonReturn: {
// "Cancel" button.
[self close];
break;
}
default:
break;
}
return;
}
if (contextInfo == MPAlertCreateSite) {
switch (returnCode) {
case NSAlertFirstButtonReturn: {
// "Create" button.
[[MPMacAppDelegate get] addElementNamed:[self.siteField stringValue] completion:^(MPElementEntity *element) {
if (element)
PearlMainQueue( ^{ [self updateElements]; } );
}];
break;
}
case NSAlertThirdButtonReturn:
// "Cancel" button.
break;
default:
break;
}
}
}
#pragma mark - NSCollectionViewDelegate
#pragma mark - NSTextFieldDelegate
- (void)doCommandBySelector:(SEL)commandSelector {
if (commandSelector == @selector(insertNewline:))
[self useSite];
}
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
if (commandSelector == @selector(cancel:))
[self close];
if (commandSelector == @selector(moveUp:))
self.elementSelectionIndexes =
[NSIndexSet indexSetWithIndex:MAX(self.elementSelectionIndexes.firstIndex, (NSUInteger)1) - 1];
if (commandSelector == @selector(moveDown:))
self.elementSelectionIndexes =
[NSIndexSet indexSetWithIndex:MIN(self.elementSelectionIndexes.firstIndex + 1, self.elements.count - 1)];
if (commandSelector == @selector(moveLeft:))
[[self selectedView].animator setBoundsOrigin:NSZeroPoint];
if (commandSelector == @selector(moveRight:))
[[self selectedView].animator setBoundsOrigin:NSMakePoint( self.siteCollectionView.frame.size.width / 2, 0 )];
if (commandSelector == @selector(insertNewline:))
[self useSite];
else
return NO;
return YES;
}
- (void)controlTextDidChange:(NSNotification *)note {
if (note.object != self.siteField)
return;
// Update the site content as the site name changes.
if ([[NSApp currentEvent] type] == NSKeyDown &&
[[[NSApp currentEvent] charactersIgnoringModifiers] isEqualToString:@"\r"]) { // Return while completing.
[self useSite];
return;
}
// if ([[NSApp currentEvent] type] == NSKeyDown &&
// [[[NSApp currentEvent] charactersIgnoringModifiers] characterAtIndex:0] == 0x1b) { // Escape while completing.
// [self trySiteWithAction:NO];
// return;
// }
[self updateElements];
}
#pragma mark - Private
- (BOOL)ensureLoadedAndUnlockedOrCloseIfLoggedOut:(BOOL)closeIfLoggedOut { - (BOOL)ensureLoadedAndUnlockedOrCloseIfLoggedOut:(BOOL)closeIfLoggedOut {
if (![self ensureStoreLoaded]) if (![self ensureStoreLoaded])
@@ -163,188 +311,35 @@
if ([MPMacAppDelegate get].key) if ([MPMacAppDelegate get].key)
return; return;
self.content = @"";
[self.siteField setStringValue:@""]; [self.siteField setStringValue:@""];
[self.typeField deselectItemAtIndex:[self.typeField indexOfSelectedItem]];
[self.tipField setStringValue:@""];
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked." NSAlert *alert = [NSAlert new];
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel" [alert addButtonWithTitle:@"Unlock"];
informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@", [alert addButtonWithTitle:@"Change"];
userName]; [alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Master Password is locked."];
[alert setInformativeText:PearlString( @"The master password is required to unlock the application for:\n\n%@", userName )];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:passwordField]; [alert setAccessoryView:passwordField];
[alert layout]; [alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.window modalDelegate:self [alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP]; didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP];
[passwordField becomeFirstResponder];
}]; }];
}]; }];
return unlocked; return unlocked;
} }
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { - (void)updateElements {
if (contextInfo == MPAlertIncorrectMP) { NSString *query = [self.siteField.currentEditor string];
[self close]; if (![query length] || ![MPMacAppDelegate get].key) {
self.elements = nil;
return; return;
} }
if (contextInfo == MPAlertUnlockMP) {
switch (returnCode) {
case NSAlertAlternateReturn: {
// "Change" button.
NSInteger returnCode_ = [[NSAlert
alertWithMessageText:@"Changing Master Password" defaultButton:nil
alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil informativeTextWithFormat:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."]
runModal];
if (returnCode_ == NSAlertDefaultReturn) {
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
activeUser.keyID = nil;
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[[MPMacAppDelegate get] signOutAnimated:YES];
[context saveToStore];
}];
}
break;
}
case NSAlertOtherReturn: {
// "Cancel" button.
[self close];
break;
}
case NSAlertDefaultReturn: {
// "Unlock" button.
self.contentContainer.alphaValue = 0;
[self.progressView startAnimation:nil];
self.inProgress = YES;
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
NSString *userName = activeUser.name;
BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc
usingMasterPassword:password];
self.inProgress = NO;
dispatch_async( dispatch_get_current_queue(), ^{
[self.progressView stopAnimation:nil];
if (success)
self.contentContainer.alphaValue = 1;
else {
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
NSLocalizedDescriptionKey : PearlString( @"Incorrect master password for user %@", userName )
}]] beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertIncorrectMP];
}
} );
}];
break;
}
default:
break;
}
return;
}
if (contextInfo == MPAlertCreateSite) {
switch (returnCode) {
case NSAlertDefaultReturn: {
[[MPMacAppDelegate get] addElementNamed:[self.siteField stringValue] completion:^(MPElementEntity *element) {
if (element) {
_activeElementOID = element.objectID;
[self trySiteWithAction:NO];
}
}];
break;
}
default:
break;
}
}
if (contextInfo == MPAlertChangeType) {
switch (returnCode) {
case NSAlertDefaultReturn: {
MPElementType type = [self selectedType];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *activeElement = [self activeElementInContext:context];
_activeElementOID = [[MPMacAppDelegate get] changeElement:activeElement inContext:context
toType:type].objectID;
[context saveToStore];
dispatch_async( dispatch_get_main_queue(), ^{
[self trySiteWithAction:NO];
} );
}];
break;
}
default:
break;
}
}
}
- (MPElementType)selectedType {
if (self.typeField.indexOfSelectedItem == 0)
return MPElementTypeGeneratedMaximum;
if (self.typeField.indexOfSelectedItem == 1)
return MPElementTypeGeneratedLong;
if (self.typeField.indexOfSelectedItem == 2)
return MPElementTypeGeneratedMedium;
if (self.typeField.indexOfSelectedItem == 3)
return MPElementTypeGeneratedBasic;
if (self.typeField.indexOfSelectedItem == 4)
return MPElementTypeGeneratedShort;
if (self.typeField.indexOfSelectedItem == 5)
return MPElementTypeGeneratedPIN;
if (self.typeField.indexOfSelectedItem == 6)
return MPElementTypeStoredPersonal;
if (self.typeField.indexOfSelectedItem == 7)
return MPElementTypeStoredDevicePrivate;
wrn(@"Unsupported type selected: %li, assuming Long.", self.typeField.indexOfSelectedItem);
return MPElementTypeGeneratedLong;
}
- (void)comboBoxSelectionDidChange:(NSNotification *)notification {
if (notification.object == self.typeField) {
if ([self.typeField indexOfSelectedItem] < 0)
return;
MPElementEntity *activeElement = [self activeElementForMainThread];
MPElementType selectedType = [self selectedType];
if (!activeElement || activeElement.type == selectedType || !(selectedType & MPElementTypeClassGenerated))
return;
[[NSAlert alertWithMessageText:@"Change Password Type" defaultButton:@"Change Password"
alternateButton:@"Cancel" otherButton:nil
informativeTextWithFormat:@"Changing the password type for this site will cause the password to change.\n"
@"You will need to update your account with the new password."]
beginSheetModalForWindow:self.window modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:MPAlertChangeType];
}
}
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words
forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
NSString *query = [[textView string] substringWithRange:charRange];
if (![query length] || ![MPMacAppDelegate get].key)
return nil;
__block NSMutableArray *mutableResults = [NSMutableArray array];
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { [MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]];
@@ -353,162 +348,77 @@
NSError *error = nil; NSError *error = nil;
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error]; NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
if (!siteResults) if (!siteResults) {
err(@"While fetching elements for completion: %@", error); err(@"While fetching elements for completion: %@", error);
else if ([siteResults count]) { return;
_activeElementOID = ((NSManagedObject *)[siteResults objectAtIndex:0]).objectID;
for (MPElementEntity *element in siteResults)
[mutableResults addObject:element.name];
} }
else
_activeElementOID = nil; NSMutableArray *newElements = [NSMutableArray arrayWithCapacity:[siteResults count]];
for (MPElementEntity *element in siteResults)
[newElements addObject:[[MPElementModel alloc] initWithEntity:element]];
self.elements = newElements;
if (!self.selectedElement)
self.elementSelectionIndexes = [newElements count]? [NSIndexSet indexSetWithIndex:0]: nil;
}]; }];
if ([mutableResults count] < 2) {
//[textView setString:[(MPElementEntity *)[siteResults objectAtIndex:0] name]];
//[textView setSelectedRange:NSMakeRange( [query length], [[textView string] length] - [query length] )];
[self trySiteWithAction:NO];
}
return mutableResults;
} }
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector { - (NSUInteger)selectedIndex {
if (commandSelector == @selector(cancel:)) { // Escape without completion. if (!self.elementSelectionIndexes)
[self close]; return NSNotFound;
return YES; NSUInteger selectedIndex = self.elementSelectionIndexes.firstIndex;
} if (selectedIndex >= self.elements.count)
if ((self.siteFieldPreventCompletion = [NSStringFromSelector( commandSelector ) hasPrefix:@"delete"])) { // Backspace any time. return NSNotFound;
_activeElementOID = nil;
[self trySiteWithAction:NO];
return NO;
}
if (commandSelector == @selector(insertNewline:)) { // Return without completion.
[self trySiteWithAction:YES];
return YES;
}
return NO; return selectedIndex;
} }
- (void)controlTextDidEndEditing:(NSNotification *)note { - (NSBox *)selectedView {
if (note.object != self.siteField) NSUInteger selectedIndex = [self selectedIndex];
return; if (selectedIndex == NSNotFound)
[self trySiteWithAction:NO];
}
- (void)controlTextDidChange:(NSNotification *)note {
if (note.object != self.siteField)
return;
// Update the site content as the site name changes.
if ([[NSApp currentEvent] type] == NSKeyDown &&
[[[NSApp currentEvent] charactersIgnoringModifiers] isEqualToString:@"\r"]) { // Return while completing.
[self trySiteWithAction:YES];
return;
}
if ([[NSApp currentEvent] type] == NSKeyDown &&
[[[NSApp currentEvent] charactersIgnoringModifiers] characterAtIndex:0] == 0x1b) { // Escape while completing.
_activeElementOID = nil;
[self trySiteWithAction:NO];
return;
}
if (self.siteFieldPreventCompletion) {
self.siteFieldPreventCompletion = NO;
return;
}
self.siteFieldPreventCompletion = YES;
[(NSText *)[note.userInfo objectForKey:@"NSFieldEditor"] complete:self];
self.siteFieldPreventCompletion = NO;
}
- (MPElementEntity *)activeElementForMainThread {
return [self activeElementInContext:[MPMacAppDelegate managedObjectContextForMainThreadIfReady]];
}
- (MPElementEntity *)activeElementInContext:(NSManagedObjectContext *)moc {
if (!_activeElementOID)
return nil; return nil;
NSError *error; return (NSBox *)[self.siteCollectionView itemAtIndex:selectedIndex].view;
MPElementEntity *activeElement = (MPElementEntity *)[moc existingObjectWithID:_activeElementOID error:&error];
if (!activeElement)
err(@"Couldn't retrieve active element: %@", error);
return activeElement;
} }
- (void)setContent:(NSString *)content { - (MPElementModel *)selectedElement {
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new]; NSUInteger selectedIndex = [self selectedIndex];
paragraph.alignment = NSCenterTextAlignment; if (selectedIndex == NSNotFound)
return nil;
[self.contentField setAttributedStringValue:[[NSAttributedString alloc] initWithString:content attributes:@{ return (MPElementModel *)self.elements[selectedIndex];
NSParagraphStyleAttributeName : paragraph
}]];
} }
- (void)trySiteWithAction:(BOOL)doAction { - (void)setSelectedElement:(MPElementModel *)element {
NSString *siteName = [self.siteField stringValue]; self.elementSelectionIndexes = [NSIndexSet indexSetWithIndex:[self.elements indexOfObject:element]];
[self.progressView startAnimation:nil]; }
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
BOOL actionHandled = NO;
MPElementEntity *activeElement = [self activeElementInContext:context];
NSString *content = [activeElement.content description];
NSString *typeName = [activeElement typeShortName];
if (!content)
content = @"";
if (doAction) { - (void)useSite {
if ([content length]) {
// Performing action while content is available. Copy it. MPElementModel *selectedElement = [self selectedElement];
[self copyContent:content]; if (selectedElement) {
} // Performing action while content is available. Copy it.
else if ([siteName length]) { [self copyContent:selectedElement.content];
[self close];
NSUserNotification *notification = [NSUserNotification new];
notification.title = @"Password Copied";
if (selectedElement.loginName.length)
notification.subtitle = PearlString( @"%@ at %@", selectedElement.loginName, selectedElement.site );
else
notification.subtitle = selectedElement.site;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
else {
NSString *siteName = [self.siteField stringValue];
if ([siteName length])
// Performing action without content but a site name is written. // Performing action without content but a site name is written.
[self createNewSite:siteName]; [self createNewSite:siteName];
actionHandled = YES; }
}
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self setContent:content];
[self.progressView stopAnimation:nil];
if (![[self.typeField stringValue] isEqualToString:typeName])
[self.typeField selectItemWithObjectValue:typeName];
self.tipField.alphaValue = 1;
if (actionHandled)
[self.tipField setStringValue:@""];
else if ([content length] == 0) {
if ([siteName length])
[self.tipField setStringValue:@"Hit ⌤ (ENTER) to create a new site."];
else
[self.tipField setStringValue:@""];
}
else if (!doAction)
[self.tipField setStringValue:@"Hit ⌤ (ENTER) to copy the password."];
else {
[self.tipField setStringValue:@"Copied! Hit ⎋ (ESC) to close window."];
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC) ), dispatch_get_main_queue(), ^{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.2f];
[self.tipField.animator setAlphaValue:0];
[NSAnimationContext endGrouping];
} );
}
}];
}];
} }
- (void)copyContent:(NSString *)content { - (void)copyContent:(NSString *)content {
@@ -520,7 +430,7 @@
} }
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
[[self activeElementInContext:moc] use]; [[self.selectedElement entityInContext:moc] use];
[moc saveToStore]; [moc saveToStore];
}]; }];
} }
@@ -528,12 +438,38 @@
- (void)createNewSite:(NSString *)siteName { - (void)createNewSite:(NSString *)siteName {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSAlert *alert = [NSAlert alertWithMessageText:@"Create site?" NSAlert *alert = [NSAlert new];
defaultButton:@"Create" alternateButton:nil otherButton:@"Cancel" [alert addButtonWithTitle:@"Create"];
informativeTextWithFormat:@"Do you want to create a new site named:\n\n%@", siteName]; [alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Create site?"];
[alert setInformativeText:PearlString( @"Do you want to create a new site named:\n\n%@", siteName )];
[alert beginSheetModalForWindow:self.window modalDelegate:self [alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertCreateSite]; didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertCreateSite];
}]; }];
} }
#pragma mark - KVO
- (void)setElementSelectionIndexes:(NSIndexSet *)elementSelectionIndexes {
// First reset bounds.
PearlMainThread(^{
NSUInteger selectedIndex = self.elementSelectionIndexes.firstIndex;
if (selectedIndex != NSNotFound && selectedIndex < self.elements.count)
[[self selectedView].animator setBoundsOrigin:NSZeroPoint];
} );
_elementSelectionIndexes = elementSelectionIndexes;
}
- (void)insertObject:(MPElementModel *)model inElementsAtIndex:(NSUInteger)index {
[self.elements insertObject:model atIndex:index];
}
- (void)removeObjectFromElementsAtIndex:(NSUInteger)index {
[self.elements removeObjectAtIndex:index];
}
@end @end

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,8 @@
<string>Master Password</string> <string>Master Password</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string> <string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>MasterPassword</string> <string>MasterPassword</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.lyndir.lhunath.MasterPassword.Mac</string> <string>com.lyndir.lhunath.MasterPassword.Mac</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>

View File

@@ -10,8 +10,9 @@
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; }; 93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; }; 93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */; };
93D39C7C2BE7C0E0763B0177 /* MPElementCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; }; 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
DA0933CA1747A56A00DE1CEF /* MPInitialWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */; };
DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */; }; DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */; };
DA0933D01747B91B00DE1CEF /* appstore.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CF1747B91B00DE1CEF /* appstore.png */; }; DA0933D01747B91B00DE1CEF /* appstore.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CF1747B91B00DE1CEF /* appstore.png */; };
DA16B33F170661D4000A0EAB /* libUbiquityStoreManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4425CB1557BED40052177D /* libUbiquityStoreManager.a */; }; DA16B33F170661D4000A0EAB /* libUbiquityStoreManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4425CB1557BED40052177D /* libUbiquityStoreManager.a */; };
@@ -20,6 +21,12 @@
DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA16B343170661EE000A0EAB /* Cocoa.framework */; }; DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA16B343170661EE000A0EAB /* Cocoa.framework */; };
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
DA1E4D50176E0E280065E0EF /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA1E4D4F176E0E280065E0EF /* Media.xcassets */; }; DA1E4D50176E0E280065E0EF /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA1E4D4F176E0E280065E0EF /* Media.xcassets */; };
DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */; };
DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */; };
DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */; };
DA2CA4F018D323D3007798F8 /* NSArray+Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */; };
DA2CA4F118D323D3007798F8 /* NSTimer+PearlBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */; };
DA2CA4F218D323D3007798F8 /* NSTimer+PearlBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */; };
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; }; DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; }; DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; }; DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
@@ -32,15 +39,6 @@
DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; }; DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; };
DA5E5C8817248AA1003798D8 /* crypto_aesctr.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C7B17248AA1003798D8 /* crypto_aesctr.h */; };
DA5E5C8917248AA1003798D8 /* crypto_scrypt.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C7C17248AA1003798D8 /* crypto_scrypt.h */; };
DA5E5C8A17248AA1003798D8 /* memlimit.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C7D17248AA1003798D8 /* memlimit.h */; };
DA5E5C8B17248AA1003798D8 /* readpass.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C7E17248AA1003798D8 /* readpass.h */; };
DA5E5C8C17248AA1003798D8 /* scryptenc.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C7F17248AA1003798D8 /* scryptenc.h */; };
DA5E5C8D17248AA1003798D8 /* scryptenc_cpuperf.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C8017248AA1003798D8 /* scryptenc_cpuperf.h */; };
DA5E5C8E17248AA1003798D8 /* sha256.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C8117248AA1003798D8 /* sha256.h */; };
DA5E5C8F17248AA1003798D8 /* sysendian.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C8217248AA1003798D8 /* sysendian.h */; };
DA5E5C9017248AA1003798D8 /* warn.h in Headers */ = {isa = PBXBuildFile; fileRef = DA5E5C8317248AA1003798D8 /* warn.h */; };
DA5E5C9417248AA1003798D8 /* libscryptenc-osx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5E5C8717248AA1003798D8 /* libscryptenc-osx.a */; }; DA5E5C9417248AA1003798D8 /* libscryptenc-osx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5E5C8717248AA1003798D8 /* libscryptenc-osx.a */; };
DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5C981724A667003798D8 /* MPAlgorithm.m */; }; DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5C981724A667003798D8 /* MPAlgorithm.m */; };
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5C9A1724A667003798D8 /* MPAlgorithmV0.m */; }; DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5C9A1724A667003798D8 /* MPAlgorithmV0.m */; };
@@ -130,6 +128,88 @@
DACA299A1705E2BD002C6C22 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA298C1705E2BD002C6C22 /* JRSwizzle.m */; }; DACA299A1705E2BD002C6C22 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA298C1705E2BD002C6C22 /* JRSwizzle.m */; };
DAD9B5F01762CAA4001835F9 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD9B5EF1762CAA4001835F9 /* ServiceManagement.framework */; }; DAD9B5F01762CAA4001835F9 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD9B5EF1762CAA4001835F9 /* ServiceManagement.framework */; };
DAD9B5F11762CAB9001835F9 /* MasterPassword-Mac-LoginHelper.app in Copy LoginHelper */ = {isa = PBXBuildFile; fileRef = DAD9B5E6176299BA001835F9 /* MasterPassword-Mac-LoginHelper.app */; }; DAD9B5F11762CAB9001835F9 /* MasterPassword-Mac-LoginHelper.app in Copy LoginHelper */ = {isa = PBXBuildFile; fileRef = DAD9B5E6176299BA001835F9 /* MasterPassword-Mac-LoginHelper.app */; };
DAEB93D918AB0FFD000490CC /* aes.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938718AB0FFD000490CC /* aes.h */; };
DAEB93DA18AB0FFD000490CC /* asn1.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938818AB0FFD000490CC /* asn1.h */; };
DAEB93DB18AB0FFD000490CC /* asn1_mac.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938918AB0FFD000490CC /* asn1_mac.h */; };
DAEB93DC18AB0FFD000490CC /* asn1t.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938A18AB0FFD000490CC /* asn1t.h */; };
DAEB93DD18AB0FFD000490CC /* bio.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938B18AB0FFD000490CC /* bio.h */; };
DAEB93DE18AB0FFD000490CC /* blowfish.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938C18AB0FFD000490CC /* blowfish.h */; };
DAEB93DF18AB0FFD000490CC /* bn.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938D18AB0FFD000490CC /* bn.h */; };
DAEB93E018AB0FFD000490CC /* buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938E18AB0FFD000490CC /* buffer.h */; };
DAEB93E118AB0FFD000490CC /* camellia.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB938F18AB0FFD000490CC /* camellia.h */; };
DAEB93E218AB0FFD000490CC /* cast.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939018AB0FFD000490CC /* cast.h */; };
DAEB93E318AB0FFD000490CC /* cms.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939118AB0FFD000490CC /* cms.h */; };
DAEB93E418AB0FFD000490CC /* comp.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939218AB0FFD000490CC /* comp.h */; };
DAEB93E518AB0FFD000490CC /* conf.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939318AB0FFD000490CC /* conf.h */; };
DAEB93E618AB0FFD000490CC /* conf_api.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939418AB0FFD000490CC /* conf_api.h */; };
DAEB93E718AB0FFD000490CC /* crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939518AB0FFD000490CC /* crypto.h */; };
DAEB93E818AB0FFD000490CC /* des.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939618AB0FFD000490CC /* des.h */; };
DAEB93E918AB0FFD000490CC /* des_old.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939718AB0FFD000490CC /* des_old.h */; };
DAEB93EA18AB0FFD000490CC /* dh.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939818AB0FFD000490CC /* dh.h */; };
DAEB93EB18AB0FFD000490CC /* dsa.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939918AB0FFD000490CC /* dsa.h */; };
DAEB93EC18AB0FFD000490CC /* dso.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939A18AB0FFD000490CC /* dso.h */; };
DAEB93ED18AB0FFD000490CC /* dtls1.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939B18AB0FFD000490CC /* dtls1.h */; };
DAEB93EE18AB0FFD000490CC /* e_os2.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939C18AB0FFD000490CC /* e_os2.h */; };
DAEB93EF18AB0FFD000490CC /* ebcdic.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939D18AB0FFD000490CC /* ebcdic.h */; };
DAEB93F018AB0FFD000490CC /* ec.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939E18AB0FFD000490CC /* ec.h */; };
DAEB93F118AB0FFD000490CC /* ecdh.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB939F18AB0FFD000490CC /* ecdh.h */; };
DAEB93F218AB0FFD000490CC /* ecdsa.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A018AB0FFD000490CC /* ecdsa.h */; };
DAEB93F318AB0FFD000490CC /* engine.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A118AB0FFD000490CC /* engine.h */; };
DAEB93F418AB0FFD000490CC /* err.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A218AB0FFD000490CC /* err.h */; };
DAEB93F518AB0FFD000490CC /* evp.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A318AB0FFD000490CC /* evp.h */; };
DAEB93F618AB0FFD000490CC /* hmac.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A418AB0FFD000490CC /* hmac.h */; };
DAEB93F718AB0FFD000490CC /* idea.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A518AB0FFD000490CC /* idea.h */; };
DAEB93F818AB0FFD000490CC /* krb5_asn.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A618AB0FFD000490CC /* krb5_asn.h */; };
DAEB93F918AB0FFD000490CC /* kssl.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A718AB0FFD000490CC /* kssl.h */; };
DAEB93FA18AB0FFD000490CC /* lhash.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A818AB0FFD000490CC /* lhash.h */; };
DAEB93FB18AB0FFD000490CC /* md4.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93A918AB0FFD000490CC /* md4.h */; };
DAEB93FC18AB0FFD000490CC /* md5.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93AA18AB0FFD000490CC /* md5.h */; };
DAEB93FD18AB0FFD000490CC /* mdc2.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93AB18AB0FFD000490CC /* mdc2.h */; };
DAEB93FE18AB0FFD000490CC /* modes.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93AC18AB0FFD000490CC /* modes.h */; };
DAEB93FF18AB0FFD000490CC /* obj_mac.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93AD18AB0FFD000490CC /* obj_mac.h */; };
DAEB940018AB0FFD000490CC /* objects.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93AE18AB0FFD000490CC /* objects.h */; };
DAEB940118AB0FFD000490CC /* ocsp.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93AF18AB0FFD000490CC /* ocsp.h */; };
DAEB940218AB0FFD000490CC /* opensslconf.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B018AB0FFD000490CC /* opensslconf.h */; };
DAEB940318AB0FFD000490CC /* opensslv.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B118AB0FFD000490CC /* opensslv.h */; };
DAEB940418AB0FFD000490CC /* ossl_typ.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B218AB0FFD000490CC /* ossl_typ.h */; };
DAEB940518AB0FFD000490CC /* pem.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B318AB0FFD000490CC /* pem.h */; };
DAEB940618AB0FFD000490CC /* pem2.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B418AB0FFD000490CC /* pem2.h */; };
DAEB940718AB0FFD000490CC /* pkcs12.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B518AB0FFD000490CC /* pkcs12.h */; };
DAEB940818AB0FFD000490CC /* pkcs7.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B618AB0FFD000490CC /* pkcs7.h */; };
DAEB940918AB0FFD000490CC /* pqueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B718AB0FFD000490CC /* pqueue.h */; };
DAEB940A18AB0FFD000490CC /* rand.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B818AB0FFD000490CC /* rand.h */; };
DAEB940B18AB0FFD000490CC /* rc2.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93B918AB0FFD000490CC /* rc2.h */; };
DAEB940C18AB0FFD000490CC /* rc4.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93BA18AB0FFD000490CC /* rc4.h */; };
DAEB940D18AB0FFD000490CC /* ripemd.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93BB18AB0FFD000490CC /* ripemd.h */; };
DAEB940E18AB0FFD000490CC /* rsa.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93BC18AB0FFD000490CC /* rsa.h */; };
DAEB940F18AB0FFD000490CC /* safestack.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93BD18AB0FFD000490CC /* safestack.h */; };
DAEB941018AB0FFD000490CC /* seed.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93BE18AB0FFD000490CC /* seed.h */; };
DAEB941118AB0FFD000490CC /* sha.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93BF18AB0FFD000490CC /* sha.h */; };
DAEB941218AB0FFD000490CC /* ssl.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C018AB0FFD000490CC /* ssl.h */; };
DAEB941318AB0FFD000490CC /* ssl2.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C118AB0FFD000490CC /* ssl2.h */; };
DAEB941418AB0FFD000490CC /* ssl23.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C218AB0FFD000490CC /* ssl23.h */; };
DAEB941518AB0FFD000490CC /* ssl3.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C318AB0FFD000490CC /* ssl3.h */; };
DAEB941618AB0FFD000490CC /* stack.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C418AB0FFD000490CC /* stack.h */; };
DAEB941718AB0FFD000490CC /* symhacks.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C518AB0FFD000490CC /* symhacks.h */; };
DAEB941818AB0FFD000490CC /* tls1.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C618AB0FFD000490CC /* tls1.h */; };
DAEB941918AB0FFD000490CC /* ts.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C718AB0FFD000490CC /* ts.h */; };
DAEB941A18AB0FFD000490CC /* txt_db.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C818AB0FFD000490CC /* txt_db.h */; };
DAEB941B18AB0FFD000490CC /* ui.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93C918AB0FFD000490CC /* ui.h */; };
DAEB941C18AB0FFD000490CC /* ui_compat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93CA18AB0FFD000490CC /* ui_compat.h */; };
DAEB941D18AB0FFD000490CC /* whrlpool.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93CB18AB0FFD000490CC /* whrlpool.h */; };
DAEB941E18AB0FFD000490CC /* x509.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93CC18AB0FFD000490CC /* x509.h */; };
DAEB941F18AB0FFD000490CC /* x509_vfy.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93CD18AB0FFD000490CC /* x509_vfy.h */; };
DAEB942018AB0FFD000490CC /* x509v3.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93CE18AB0FFD000490CC /* x509v3.h */; };
DAEB942118AB0FFD000490CC /* crypto_aesctr.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D018AB0FFD000490CC /* crypto_aesctr.h */; };
DAEB942218AB0FFD000490CC /* crypto_scrypt.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D118AB0FFD000490CC /* crypto_scrypt.h */; };
DAEB942318AB0FFD000490CC /* memlimit.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D218AB0FFD000490CC /* memlimit.h */; };
DAEB942418AB0FFD000490CC /* readpass.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D318AB0FFD000490CC /* readpass.h */; };
DAEB942518AB0FFD000490CC /* scryptenc.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D418AB0FFD000490CC /* scryptenc.h */; };
DAEB942618AB0FFD000490CC /* scryptenc_cpuperf.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D518AB0FFD000490CC /* scryptenc_cpuperf.h */; };
DAEB942718AB0FFD000490CC /* sha256.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D618AB0FFD000490CC /* sha256.h */; };
DAEB942818AB0FFD000490CC /* sysendian.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D718AB0FFD000490CC /* sysendian.h */; };
DAEB942918AB0FFD000490CC /* warn.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D818AB0FFD000490CC /* warn.h */; };
DAEB942E18B47FB3000490CC /* MPInitialWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */; };
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */; }; DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */; };
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */; }; DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */; };
DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */; }; DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */; };
@@ -221,15 +301,25 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; }; 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
93D39240B5143E01F0B75E96 /* MPElementModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementModel.h; sourceTree = "<group>"; };
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; }; 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementCollectionView.m; sourceTree = "<group>"; };
93D3960D320FF8A072B092E3 /* MPElementCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementCollectionView.h; sourceTree = "<group>"; };
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; }; 93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; }; 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementModel.m; sourceTree = "<group>"; };
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInitialWindow.xib; sourceTree = "<group>"; }; DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInitialWindow.xib; sourceTree = "<group>"; };
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shot-laptop-leaning-iphone.png"; sourceTree = "<group>"; }; DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shot-laptop-leaning-iphone.png"; sourceTree = "<group>"; };
DA0933CF1747B91B00DE1CEF /* appstore.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = appstore.png; sourceTree = "<group>"; }; DA0933CF1747B91B00DE1CEF /* appstore.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = appstore.png; sourceTree = "<group>"; };
DA16B340170661DB000A0EAB /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; DA16B340170661DB000A0EAB /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
DA16B343170661EE000A0EAB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; DA16B343170661EE000A0EAB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
DA1E4D4F176E0E280065E0EF /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Media.xcassets; path = MasterPassword/Media.xcassets; sourceTree = "<group>"; }; DA1E4D4F176E0E280065E0EF /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Media.xcassets; path = MasterPassword/Media.xcassets; sourceTree = "<group>"; };
DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = "<group>"; };
DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Pearl.m"; sourceTree = "<group>"; };
DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Pearl.h"; sourceTree = "<group>"; };
DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+PearlBlock.m"; sourceTree = "<group>"; };
DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+PearlBlock.h"; sourceTree = "<group>"; };
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; }; DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; }; DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; }; DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
@@ -245,15 +335,6 @@
DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
DA5BFA4E147E415C00F98B1E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; DA5BFA4E147E415C00F98B1E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
DA5E5C7B17248AA1003798D8 /* crypto_aesctr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto_aesctr.h; sourceTree = "<group>"; };
DA5E5C7C17248AA1003798D8 /* crypto_scrypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto_scrypt.h; sourceTree = "<group>"; };
DA5E5C7D17248AA1003798D8 /* memlimit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = memlimit.h; sourceTree = "<group>"; };
DA5E5C7E17248AA1003798D8 /* readpass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = readpass.h; sourceTree = "<group>"; };
DA5E5C7F17248AA1003798D8 /* scryptenc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scryptenc.h; sourceTree = "<group>"; };
DA5E5C8017248AA1003798D8 /* scryptenc_cpuperf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scryptenc_cpuperf.h; sourceTree = "<group>"; };
DA5E5C8117248AA1003798D8 /* sha256.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha256.h; sourceTree = "<group>"; };
DA5E5C8217248AA1003798D8 /* sysendian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sysendian.h; sourceTree = "<group>"; };
DA5E5C8317248AA1003798D8 /* warn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = warn.h; sourceTree = "<group>"; };
DA5E5C8717248AA1003798D8 /* libscryptenc-osx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-osx.a"; sourceTree = "<group>"; }; DA5E5C8717248AA1003798D8 /* libscryptenc-osx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-osx.a"; sourceTree = "<group>"; };
DA5E5C971724A667003798D8 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = "<group>"; }; DA5E5C971724A667003798D8 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = "<group>"; };
DA5E5C981724A667003798D8 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = "<group>"; }; DA5E5C981724A667003798D8 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = "<group>"; };
@@ -373,6 +454,87 @@
DAD312C01552A20800A3F9ED /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; DAD312C01552A20800A3F9ED /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
DAD9B5E1176299B9001835F9 /* MasterPassword-Mac-LoginHelper.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "MasterPassword-Mac-LoginHelper.xcodeproj"; path = "MasterPassword-Mac-LoginHelper/MasterPassword-Mac-LoginHelper.xcodeproj"; sourceTree = "<group>"; }; DAD9B5E1176299B9001835F9 /* MasterPassword-Mac-LoginHelper.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "MasterPassword-Mac-LoginHelper.xcodeproj"; path = "MasterPassword-Mac-LoginHelper/MasterPassword-Mac-LoginHelper.xcodeproj"; sourceTree = "<group>"; };
DAD9B5EF1762CAA4001835F9 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; DAD9B5EF1762CAA4001835F9 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; };
DAEB938718AB0FFD000490CC /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = "<group>"; };
DAEB938818AB0FFD000490CC /* asn1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = asn1.h; sourceTree = "<group>"; };
DAEB938918AB0FFD000490CC /* asn1_mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = asn1_mac.h; sourceTree = "<group>"; };
DAEB938A18AB0FFD000490CC /* asn1t.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = asn1t.h; sourceTree = "<group>"; };
DAEB938B18AB0FFD000490CC /* bio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bio.h; sourceTree = "<group>"; };
DAEB938C18AB0FFD000490CC /* blowfish.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = blowfish.h; sourceTree = "<group>"; };
DAEB938D18AB0FFD000490CC /* bn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bn.h; sourceTree = "<group>"; };
DAEB938E18AB0FFD000490CC /* buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = buffer.h; sourceTree = "<group>"; };
DAEB938F18AB0FFD000490CC /* camellia.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = camellia.h; sourceTree = "<group>"; };
DAEB939018AB0FFD000490CC /* cast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cast.h; sourceTree = "<group>"; };
DAEB939118AB0FFD000490CC /* cms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cms.h; sourceTree = "<group>"; };
DAEB939218AB0FFD000490CC /* comp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = comp.h; sourceTree = "<group>"; };
DAEB939318AB0FFD000490CC /* conf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = conf.h; sourceTree = "<group>"; };
DAEB939418AB0FFD000490CC /* conf_api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = conf_api.h; sourceTree = "<group>"; };
DAEB939518AB0FFD000490CC /* crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto.h; sourceTree = "<group>"; };
DAEB939618AB0FFD000490CC /* des.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = des.h; sourceTree = "<group>"; };
DAEB939718AB0FFD000490CC /* des_old.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = des_old.h; sourceTree = "<group>"; };
DAEB939818AB0FFD000490CC /* dh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dh.h; sourceTree = "<group>"; };
DAEB939918AB0FFD000490CC /* dsa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dsa.h; sourceTree = "<group>"; };
DAEB939A18AB0FFD000490CC /* dso.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dso.h; sourceTree = "<group>"; };
DAEB939B18AB0FFD000490CC /* dtls1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dtls1.h; sourceTree = "<group>"; };
DAEB939C18AB0FFD000490CC /* e_os2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = e_os2.h; sourceTree = "<group>"; };
DAEB939D18AB0FFD000490CC /* ebcdic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ebcdic.h; sourceTree = "<group>"; };
DAEB939E18AB0FFD000490CC /* ec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ec.h; sourceTree = "<group>"; };
DAEB939F18AB0FFD000490CC /* ecdh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ecdh.h; sourceTree = "<group>"; };
DAEB93A018AB0FFD000490CC /* ecdsa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ecdsa.h; sourceTree = "<group>"; };
DAEB93A118AB0FFD000490CC /* engine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = engine.h; sourceTree = "<group>"; };
DAEB93A218AB0FFD000490CC /* err.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = err.h; sourceTree = "<group>"; };
DAEB93A318AB0FFD000490CC /* evp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evp.h; sourceTree = "<group>"; };
DAEB93A418AB0FFD000490CC /* hmac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hmac.h; sourceTree = "<group>"; };
DAEB93A518AB0FFD000490CC /* idea.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = idea.h; sourceTree = "<group>"; };
DAEB93A618AB0FFD000490CC /* krb5_asn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = krb5_asn.h; sourceTree = "<group>"; };
DAEB93A718AB0FFD000490CC /* kssl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = kssl.h; sourceTree = "<group>"; };
DAEB93A818AB0FFD000490CC /* lhash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lhash.h; sourceTree = "<group>"; };
DAEB93A918AB0FFD000490CC /* md4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md4.h; sourceTree = "<group>"; };
DAEB93AA18AB0FFD000490CC /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = "<group>"; };
DAEB93AB18AB0FFD000490CC /* mdc2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdc2.h; sourceTree = "<group>"; };
DAEB93AC18AB0FFD000490CC /* modes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modes.h; sourceTree = "<group>"; };
DAEB93AD18AB0FFD000490CC /* obj_mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = obj_mac.h; sourceTree = "<group>"; };
DAEB93AE18AB0FFD000490CC /* objects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = objects.h; sourceTree = "<group>"; };
DAEB93AF18AB0FFD000490CC /* ocsp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ocsp.h; sourceTree = "<group>"; };
DAEB93B018AB0FFD000490CC /* opensslconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opensslconf.h; sourceTree = "<group>"; };
DAEB93B118AB0FFD000490CC /* opensslv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opensslv.h; sourceTree = "<group>"; };
DAEB93B218AB0FFD000490CC /* ossl_typ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ossl_typ.h; sourceTree = "<group>"; };
DAEB93B318AB0FFD000490CC /* pem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pem.h; sourceTree = "<group>"; };
DAEB93B418AB0FFD000490CC /* pem2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pem2.h; sourceTree = "<group>"; };
DAEB93B518AB0FFD000490CC /* pkcs12.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pkcs12.h; sourceTree = "<group>"; };
DAEB93B618AB0FFD000490CC /* pkcs7.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pkcs7.h; sourceTree = "<group>"; };
DAEB93B718AB0FFD000490CC /* pqueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pqueue.h; sourceTree = "<group>"; };
DAEB93B818AB0FFD000490CC /* rand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rand.h; sourceTree = "<group>"; };
DAEB93B918AB0FFD000490CC /* rc2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rc2.h; sourceTree = "<group>"; };
DAEB93BA18AB0FFD000490CC /* rc4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rc4.h; sourceTree = "<group>"; };
DAEB93BB18AB0FFD000490CC /* ripemd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ripemd.h; sourceTree = "<group>"; };
DAEB93BC18AB0FFD000490CC /* rsa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rsa.h; sourceTree = "<group>"; };
DAEB93BD18AB0FFD000490CC /* safestack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = safestack.h; sourceTree = "<group>"; };
DAEB93BE18AB0FFD000490CC /* seed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = seed.h; sourceTree = "<group>"; };
DAEB93BF18AB0FFD000490CC /* sha.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha.h; sourceTree = "<group>"; };
DAEB93C018AB0FFD000490CC /* ssl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ssl.h; sourceTree = "<group>"; };
DAEB93C118AB0FFD000490CC /* ssl2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ssl2.h; sourceTree = "<group>"; };
DAEB93C218AB0FFD000490CC /* ssl23.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ssl23.h; sourceTree = "<group>"; };
DAEB93C318AB0FFD000490CC /* ssl3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ssl3.h; sourceTree = "<group>"; };
DAEB93C418AB0FFD000490CC /* stack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stack.h; sourceTree = "<group>"; };
DAEB93C518AB0FFD000490CC /* symhacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = symhacks.h; sourceTree = "<group>"; };
DAEB93C618AB0FFD000490CC /* tls1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tls1.h; sourceTree = "<group>"; };
DAEB93C718AB0FFD000490CC /* ts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts.h; sourceTree = "<group>"; };
DAEB93C818AB0FFD000490CC /* txt_db.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = txt_db.h; sourceTree = "<group>"; };
DAEB93C918AB0FFD000490CC /* ui.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ui.h; sourceTree = "<group>"; };
DAEB93CA18AB0FFD000490CC /* ui_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ui_compat.h; sourceTree = "<group>"; };
DAEB93CB18AB0FFD000490CC /* whrlpool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = whrlpool.h; sourceTree = "<group>"; };
DAEB93CC18AB0FFD000490CC /* x509.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x509.h; sourceTree = "<group>"; };
DAEB93CD18AB0FFD000490CC /* x509_vfy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x509_vfy.h; sourceTree = "<group>"; };
DAEB93CE18AB0FFD000490CC /* x509v3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x509v3.h; sourceTree = "<group>"; };
DAEB93D018AB0FFD000490CC /* crypto_aesctr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto_aesctr.h; sourceTree = "<group>"; };
DAEB93D118AB0FFD000490CC /* crypto_scrypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto_scrypt.h; sourceTree = "<group>"; };
DAEB93D218AB0FFD000490CC /* memlimit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = memlimit.h; sourceTree = "<group>"; };
DAEB93D318AB0FFD000490CC /* readpass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = readpass.h; sourceTree = "<group>"; };
DAEB93D418AB0FFD000490CC /* scryptenc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scryptenc.h; sourceTree = "<group>"; };
DAEB93D518AB0FFD000490CC /* scryptenc_cpuperf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scryptenc_cpuperf.h; sourceTree = "<group>"; };
DAEB93D618AB0FFD000490CC /* sha256.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha256.h; sourceTree = "<group>"; };
DAEB93D718AB0FFD000490CC /* sysendian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sysendian.h; sourceTree = "<group>"; };
DAEB93D818AB0FFD000490CC /* warn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = warn.h; sourceTree = "<group>"; };
DAEBC45214F6364500987BF6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; DAEBC45214F6364500987BF6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlExport.h"; sourceTree = "<group>"; }; DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlExport.h"; sourceTree = "<group>"; };
DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlExport.m"; sourceTree = "<group>"; }; DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlExport.m"; sourceTree = "<group>"; };
@@ -530,28 +692,12 @@
DA5E5C7917248AA1003798D8 /* lib */ = { DA5E5C7917248AA1003798D8 /* lib */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA5E5C7A17248AA1003798D8 /* include */, DAEB938518AB0FFD000490CC /* include */,
DA5E5C8717248AA1003798D8 /* libscryptenc-osx.a */, DA5E5C8717248AA1003798D8 /* libscryptenc-osx.a */,
); );
path = lib; path = lib;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DA5E5C7A17248AA1003798D8 /* include */ = {
isa = PBXGroup;
children = (
DA5E5C7B17248AA1003798D8 /* crypto_aesctr.h */,
DA5E5C7C17248AA1003798D8 /* crypto_scrypt.h */,
DA5E5C7D17248AA1003798D8 /* memlimit.h */,
DA5E5C7E17248AA1003798D8 /* readpass.h */,
DA5E5C7F17248AA1003798D8 /* scryptenc.h */,
DA5E5C8017248AA1003798D8 /* scryptenc_cpuperf.h */,
DA5E5C8117248AA1003798D8 /* sha256.h */,
DA5E5C8217248AA1003798D8 /* sysendian.h */,
DA5E5C8317248AA1003798D8 /* warn.h */,
);
path = include;
sourceTree = "<group>";
};
DA5E5C961724A667003798D8 /* ObjC */ = { DA5E5C961724A667003798D8 /* ObjC */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -608,6 +754,10 @@
DA5E5CC41724A667003798D8 /* MainMenu.xib */, DA5E5CC41724A667003798D8 /* MainMenu.xib */,
DA5E5CC61724A667003798D8 /* main.m */, DA5E5CC61724A667003798D8 /* main.m */,
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */, DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */,
93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */,
93D3960D320FF8A072B092E3 /* MPElementCollectionView.h */,
93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */,
93D39240B5143E01F0B75E96 /* MPElementModel.h */,
); );
path = Mac; path = Mac;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -793,9 +943,119 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DAEB938518AB0FFD000490CC /* include */ = {
isa = PBXGroup;
children = (
DAEB938618AB0FFD000490CC /* openssl */,
DAEB93CF18AB0FFD000490CC /* scrypt */,
);
path = include;
sourceTree = "<group>";
};
DAEB938618AB0FFD000490CC /* openssl */ = {
isa = PBXGroup;
children = (
DAEB938718AB0FFD000490CC /* aes.h */,
DAEB938818AB0FFD000490CC /* asn1.h */,
DAEB938918AB0FFD000490CC /* asn1_mac.h */,
DAEB938A18AB0FFD000490CC /* asn1t.h */,
DAEB938B18AB0FFD000490CC /* bio.h */,
DAEB938C18AB0FFD000490CC /* blowfish.h */,
DAEB938D18AB0FFD000490CC /* bn.h */,
DAEB938E18AB0FFD000490CC /* buffer.h */,
DAEB938F18AB0FFD000490CC /* camellia.h */,
DAEB939018AB0FFD000490CC /* cast.h */,
DAEB939118AB0FFD000490CC /* cms.h */,
DAEB939218AB0FFD000490CC /* comp.h */,
DAEB939318AB0FFD000490CC /* conf.h */,
DAEB939418AB0FFD000490CC /* conf_api.h */,
DAEB939518AB0FFD000490CC /* crypto.h */,
DAEB939618AB0FFD000490CC /* des.h */,
DAEB939718AB0FFD000490CC /* des_old.h */,
DAEB939818AB0FFD000490CC /* dh.h */,
DAEB939918AB0FFD000490CC /* dsa.h */,
DAEB939A18AB0FFD000490CC /* dso.h */,
DAEB939B18AB0FFD000490CC /* dtls1.h */,
DAEB939C18AB0FFD000490CC /* e_os2.h */,
DAEB939D18AB0FFD000490CC /* ebcdic.h */,
DAEB939E18AB0FFD000490CC /* ec.h */,
DAEB939F18AB0FFD000490CC /* ecdh.h */,
DAEB93A018AB0FFD000490CC /* ecdsa.h */,
DAEB93A118AB0FFD000490CC /* engine.h */,
DAEB93A218AB0FFD000490CC /* err.h */,
DAEB93A318AB0FFD000490CC /* evp.h */,
DAEB93A418AB0FFD000490CC /* hmac.h */,
DAEB93A518AB0FFD000490CC /* idea.h */,
DAEB93A618AB0FFD000490CC /* krb5_asn.h */,
DAEB93A718AB0FFD000490CC /* kssl.h */,
DAEB93A818AB0FFD000490CC /* lhash.h */,
DAEB93A918AB0FFD000490CC /* md4.h */,
DAEB93AA18AB0FFD000490CC /* md5.h */,
DAEB93AB18AB0FFD000490CC /* mdc2.h */,
DAEB93AC18AB0FFD000490CC /* modes.h */,
DAEB93AD18AB0FFD000490CC /* obj_mac.h */,
DAEB93AE18AB0FFD000490CC /* objects.h */,
DAEB93AF18AB0FFD000490CC /* ocsp.h */,
DAEB93B018AB0FFD000490CC /* opensslconf.h */,
DAEB93B118AB0FFD000490CC /* opensslv.h */,
DAEB93B218AB0FFD000490CC /* ossl_typ.h */,
DAEB93B318AB0FFD000490CC /* pem.h */,
DAEB93B418AB0FFD000490CC /* pem2.h */,
DAEB93B518AB0FFD000490CC /* pkcs12.h */,
DAEB93B618AB0FFD000490CC /* pkcs7.h */,
DAEB93B718AB0FFD000490CC /* pqueue.h */,
DAEB93B818AB0FFD000490CC /* rand.h */,
DAEB93B918AB0FFD000490CC /* rc2.h */,
DAEB93BA18AB0FFD000490CC /* rc4.h */,
DAEB93BB18AB0FFD000490CC /* ripemd.h */,
DAEB93BC18AB0FFD000490CC /* rsa.h */,
DAEB93BD18AB0FFD000490CC /* safestack.h */,
DAEB93BE18AB0FFD000490CC /* seed.h */,
DAEB93BF18AB0FFD000490CC /* sha.h */,
DAEB93C018AB0FFD000490CC /* ssl.h */,
DAEB93C118AB0FFD000490CC /* ssl2.h */,
DAEB93C218AB0FFD000490CC /* ssl23.h */,
DAEB93C318AB0FFD000490CC /* ssl3.h */,
DAEB93C418AB0FFD000490CC /* stack.h */,
DAEB93C518AB0FFD000490CC /* symhacks.h */,
DAEB93C618AB0FFD000490CC /* tls1.h */,
DAEB93C718AB0FFD000490CC /* ts.h */,
DAEB93C818AB0FFD000490CC /* txt_db.h */,
DAEB93C918AB0FFD000490CC /* ui.h */,
DAEB93CA18AB0FFD000490CC /* ui_compat.h */,
DAEB93CB18AB0FFD000490CC /* whrlpool.h */,
DAEB93CC18AB0FFD000490CC /* x509.h */,
DAEB93CD18AB0FFD000490CC /* x509_vfy.h */,
DAEB93CE18AB0FFD000490CC /* x509v3.h */,
);
path = openssl;
sourceTree = "<group>";
};
DAEB93CF18AB0FFD000490CC /* scrypt */ = {
isa = PBXGroup;
children = (
DAEB93D018AB0FFD000490CC /* crypto_aesctr.h */,
DAEB93D118AB0FFD000490CC /* crypto_scrypt.h */,
DAEB93D218AB0FFD000490CC /* memlimit.h */,
DAEB93D318AB0FFD000490CC /* readpass.h */,
DAEB93D418AB0FFD000490CC /* scryptenc.h */,
DAEB93D518AB0FFD000490CC /* scryptenc_cpuperf.h */,
DAEB93D618AB0FFD000490CC /* sha256.h */,
DAEB93D718AB0FFD000490CC /* sysendian.h */,
DAEB93D818AB0FFD000490CC /* warn.h */,
);
path = scrypt;
sourceTree = "<group>";
};
DAFE45D715039823003ABA7C /* Pearl */ = { DAFE45D715039823003ABA7C /* Pearl */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */,
DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */,
DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */,
DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */,
DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */,
DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */,
DA3509FC15F101A500C14A8E /* PearlQueue.h */, DA3509FC15F101A500C14A8E /* PearlQueue.h */,
DA3509FD15F101A500C14A8E /* PearlQueue.m */, DA3509FD15F101A500C14A8E /* PearlQueue.m */,
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */, 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */,
@@ -896,42 +1156,117 @@
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
DAEB941818AB0FFD000490CC /* tls1.h in Headers */,
DAEB940C18AB0FFD000490CC /* rc4.h in Headers */,
DAEB93F418AB0FFD000490CC /* err.h in Headers */,
DAEB93F118AB0FFD000490CC /* ecdh.h in Headers */,
DAEB93FD18AB0FFD000490CC /* mdc2.h in Headers */,
DAEB942718AB0FFD000490CC /* sha256.h in Headers */,
DAEB940818AB0FFD000490CC /* pkcs7.h in Headers */,
DA2CA4F218D323D3007798F8 /* NSTimer+PearlBlock.h in Headers */,
DAEB93DF18AB0FFD000490CC /* bn.h in Headers */,
DAEB940718AB0FFD000490CC /* pkcs12.h in Headers */,
DAEB941A18AB0FFD000490CC /* txt_db.h in Headers */,
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */, DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */,
DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */, DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */,
DAEB942218AB0FFD000490CC /* crypto_scrypt.h in Headers */,
DAEB941018AB0FFD000490CC /* seed.h in Headers */,
DAEB942618AB0FFD000490CC /* scryptenc_cpuperf.h in Headers */,
DAFE4A1715039824003ABA7C /* NSString+PearlSEL.h in Headers */, DAFE4A1715039824003ABA7C /* NSString+PearlSEL.h in Headers */,
DAEB93F518AB0FFD000490CC /* evp.h in Headers */,
DAEB941918AB0FFD000490CC /* ts.h in Headers */,
DAEB93F818AB0FFD000490CC /* krb5_asn.h in Headers */,
DAFE4A1915039824003ABA7C /* Pearl.h in Headers */, DAFE4A1915039824003ABA7C /* Pearl.h in Headers */,
DAEB93F318AB0FFD000490CC /* engine.h in Headers */,
DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */, DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */,
DAFE4A1E15039824003ABA7C /* PearlCodeUtils.h in Headers */, DAFE4A1E15039824003ABA7C /* PearlCodeUtils.h in Headers */,
DAEB940918AB0FFD000490CC /* pqueue.h in Headers */,
DAFE4A2015039824003ABA7C /* PearlConfig.h in Headers */, DAFE4A2015039824003ABA7C /* PearlConfig.h in Headers */,
DAEB941B18AB0FFD000490CC /* ui.h in Headers */,
DAEB941D18AB0FFD000490CC /* whrlpool.h in Headers */,
DAEB940418AB0FFD000490CC /* ossl_typ.h in Headers */,
DAEB93DC18AB0FFD000490CC /* asn1t.h in Headers */,
DAFE4A2215039824003ABA7C /* PearlDeviceUtils.h in Headers */, DAFE4A2215039824003ABA7C /* PearlDeviceUtils.h in Headers */,
DAEB93E518AB0FFD000490CC /* conf.h in Headers */,
DAFE4A2415039824003ABA7C /* PearlInfoPlist.h in Headers */, DAFE4A2415039824003ABA7C /* PearlInfoPlist.h in Headers */,
DAEB940618AB0FFD000490CC /* pem2.h in Headers */,
DAFE4A2615039824003ABA7C /* PearlLogger.h in Headers */, DAFE4A2615039824003ABA7C /* PearlLogger.h in Headers */,
DA2CA4F018D323D3007798F8 /* NSArray+Pearl.h in Headers */,
DAEB93FC18AB0FFD000490CC /* md5.h in Headers */,
DAFE4A2815039824003ABA7C /* PearlMathUtils.h in Headers */, DAFE4A2815039824003ABA7C /* PearlMathUtils.h in Headers */,
DAFE4A2A15039824003ABA7C /* PearlObjectUtils.h in Headers */, DAFE4A2A15039824003ABA7C /* PearlObjectUtils.h in Headers */,
DAEB93E318AB0FFD000490CC /* cms.h in Headers */,
DAEB942518AB0FFD000490CC /* scryptenc.h in Headers */,
DAEB93FA18AB0FFD000490CC /* lhash.h in Headers */,
DAFE4A2C15039824003ABA7C /* PearlResettable.h in Headers */, DAFE4A2C15039824003ABA7C /* PearlResettable.h in Headers */,
DAEB940B18AB0FFD000490CC /* rc2.h in Headers */,
DAFE4A2D15039824003ABA7C /* PearlStrings.h in Headers */, DAFE4A2D15039824003ABA7C /* PearlStrings.h in Headers */,
DAEB93DA18AB0FFD000490CC /* asn1.h in Headers */,
DAEB93EA18AB0FFD000490CC /* dh.h in Headers */,
DAEB93F918AB0FFD000490CC /* kssl.h in Headers */,
DAFE4A2F15039824003ABA7C /* PearlStringUtils.h in Headers */, DAFE4A2F15039824003ABA7C /* PearlStringUtils.h in Headers */,
DAEB940318AB0FFD000490CC /* opensslv.h in Headers */,
DAEB93ED18AB0FFD000490CC /* dtls1.h in Headers */,
DAEB93E018AB0FFD000490CC /* buffer.h in Headers */,
DAEB940218AB0FFD000490CC /* opensslconf.h in Headers */,
DAEB93E918AB0FFD000490CC /* des_old.h in Headers */,
DAFE4A3315039824003ABA7C /* Pearl-Crypto.h in Headers */, DAFE4A3315039824003ABA7C /* Pearl-Crypto.h in Headers */,
DAEB941C18AB0FFD000490CC /* ui_compat.h in Headers */,
DAEB93E218AB0FFD000490CC /* cast.h in Headers */,
DAEB942318AB0FFD000490CC /* memlimit.h in Headers */,
DAFE4A3415039824003ABA7C /* PearlCryptUtils.h in Headers */, DAFE4A3415039824003ABA7C /* PearlCryptUtils.h in Headers */,
DAEB940018AB0FFD000490CC /* objects.h in Headers */,
DAEB93E818AB0FFD000490CC /* des.h in Headers */,
DAEB941418AB0FFD000490CC /* ssl23.h in Headers */,
DAEB93EB18AB0FFD000490CC /* dsa.h in Headers */,
DAEB941218AB0FFD000490CC /* ssl.h in Headers */,
DAEB93FE18AB0FFD000490CC /* modes.h in Headers */,
DAEB940A18AB0FFD000490CC /* rand.h in Headers */,
DAEB93EE18AB0FFD000490CC /* e_os2.h in Headers */,
DAEB940E18AB0FFD000490CC /* rsa.h in Headers */,
DAEB93E618AB0FFD000490CC /* conf_api.h in Headers */,
DAFE4A3615039824003ABA7C /* PearlKeyChain.h in Headers */, DAFE4A3615039824003ABA7C /* PearlKeyChain.h in Headers */,
DAEB941518AB0FFD000490CC /* ssl3.h in Headers */,
DAEB941618AB0FFD000490CC /* stack.h in Headers */,
DAFE4A3815039824003ABA7C /* PearlRSAKey.h in Headers */, DAFE4A3815039824003ABA7C /* PearlRSAKey.h in Headers */,
DAEB93DD18AB0FFD000490CC /* bio.h in Headers */,
DAEB942418AB0FFD000490CC /* readpass.h in Headers */,
DAEB93F018AB0FFD000490CC /* ec.h in Headers */,
DAEB93E418AB0FFD000490CC /* comp.h in Headers */,
DAFE4A3A15039824003ABA7C /* PearlSCrypt.h in Headers */, DAFE4A3A15039824003ABA7C /* PearlSCrypt.h in Headers */,
DAEB93D918AB0FFD000490CC /* aes.h in Headers */,
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */, DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */,
DAEB93FB18AB0FFD000490CC /* md4.h in Headers */,
DAEB941118AB0FFD000490CC /* sha.h in Headers */,
DAEB941F18AB0FFD000490CC /* x509_vfy.h in Headers */,
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */, DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */,
DAEB93EC18AB0FFD000490CC /* dso.h in Headers */,
DAEB940118AB0FFD000490CC /* ocsp.h in Headers */,
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */, DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */,
DAFE4A63150399FF003ABA94 /* NSDateFormatter+RFC3339.h in Headers */, DAFE4A63150399FF003ABA94 /* NSDateFormatter+RFC3339.h in Headers */,
DAEB93F718AB0FFD000490CC /* idea.h in Headers */,
DAEB940F18AB0FFD000490CC /* safestack.h in Headers */,
DAEB941E18AB0FFD000490CC /* x509.h in Headers */,
DAEB93EF18AB0FFD000490CC /* ebcdic.h in Headers */,
DAEB93DE18AB0FFD000490CC /* blowfish.h in Headers */,
DAEB941718AB0FFD000490CC /* symhacks.h in Headers */,
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */, 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */,
DAEB942118AB0FFD000490CC /* crypto_aesctr.h in Headers */,
DAEB93F218AB0FFD000490CC /* ecdsa.h in Headers */,
DAEB942018AB0FFD000490CC /* x509v3.h in Headers */,
DAEB93E118AB0FFD000490CC /* camellia.h in Headers */,
DAEB93F618AB0FFD000490CC /* hmac.h in Headers */,
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */, 93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */,
DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */, DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */,
DA5E5C8817248AA1003798D8 /* crypto_aesctr.h in Headers */, DAEB942918AB0FFD000490CC /* warn.h in Headers */,
DA5E5C8917248AA1003798D8 /* crypto_scrypt.h in Headers */, DAEB93DB18AB0FFD000490CC /* asn1_mac.h in Headers */,
DA5E5C8A17248AA1003798D8 /* memlimit.h in Headers */, DAEB940518AB0FFD000490CC /* pem.h in Headers */,
DA5E5C8B17248AA1003798D8 /* readpass.h in Headers */, DAEB942818AB0FFD000490CC /* sysendian.h in Headers */,
DA5E5C8C17248AA1003798D8 /* scryptenc.h in Headers */, DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */,
DA5E5C8D17248AA1003798D8 /* scryptenc_cpuperf.h in Headers */, DAEB93FF18AB0FFD000490CC /* obj_mac.h in Headers */,
DA5E5C8E17248AA1003798D8 /* sha256.h in Headers */, DAEB93E718AB0FFD000490CC /* crypto.h in Headers */,
DA5E5C8F17248AA1003798D8 /* sysendian.h in Headers */, DAEB941318AB0FFD000490CC /* ssl2.h in Headers */,
DA5E5C9017248AA1003798D8 /* warn.h in Headers */, DAEB940D18AB0FFD000490CC /* ripemd.h in Headers */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -1138,6 +1473,7 @@
DACA27231705DF81002C6C22 /* avatar-15@2x.png in Resources */, DACA27231705DF81002C6C22 /* avatar-15@2x.png in Resources */,
DACA27241705DF81002C6C22 /* avatar-5@2x.png in Resources */, DACA27241705DF81002C6C22 /* avatar-5@2x.png in Resources */,
DACA27251705DF81002C6C22 /* avatar-6.png in Resources */, DACA27251705DF81002C6C22 /* avatar-6.png in Resources */,
DAEB942E18B47FB3000490CC /* MPInitialWindow.xib in Resources */,
DACA27261705DF81002C6C22 /* avatar-6@2x.png in Resources */, DACA27261705DF81002C6C22 /* avatar-6@2x.png in Resources */,
DACA27271705DF81002C6C22 /* avatar-16@2x.png in Resources */, DACA27271705DF81002C6C22 /* avatar-16@2x.png in Resources */,
DACA27281705DF81002C6C22 /* avatar-10.png in Resources */, DACA27281705DF81002C6C22 /* avatar-10.png in Resources */,
@@ -1172,7 +1508,6 @@
DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */, DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */,
DA5E5D0B1724A667003798D8 /* MainMenu.xib in Resources */, DA5E5D0B1724A667003798D8 /* MainMenu.xib in Resources */,
DA5E5D551724F9C8003798D8 /* MasterPassword.iconset in Resources */, DA5E5D551724F9C8003798D8 /* MasterPassword.iconset in Resources */,
DA0933CA1747A56A00DE1CEF /* MPInitialWindow.xib in Resources */,
DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */, DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */,
DA0933D01747B91B00DE1CEF /* appstore.png in Resources */, DA0933D01747B91B00DE1CEF /* appstore.png in Resources */,
); );
@@ -1265,6 +1600,8 @@
DA5E5D051724A667003798D8 /* MPPasswordWindowController.m in Sources */, DA5E5D051724A667003798D8 /* MPPasswordWindowController.m in Sources */,
DA5E5D0C1724A667003798D8 /* main.m in Sources */, DA5E5D0C1724A667003798D8 /* main.m in Sources */,
DA5E5D0D1724A667003798D8 /* MasterPassword.xcdatamodeld in Sources */, DA5E5D0D1724A667003798D8 /* MasterPassword.xcdatamodeld in Sources */,
93D39C7C2BE7C0E0763B0177 /* MPElementCollectionView.m in Sources */,
93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -1291,6 +1628,7 @@
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */, DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */,
DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */, DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */,
DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */, DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */,
DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */,
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */, DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */,
DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */, DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */,
DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */, DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */,
@@ -1306,8 +1644,10 @@
DAFE4A3915039824003ABA7C /* PearlRSAKey.m in Sources */, DAFE4A3915039824003ABA7C /* PearlRSAKey.m in Sources */,
DAFE4A3B15039824003ABA7C /* PearlSCrypt.m in Sources */, DAFE4A3B15039824003ABA7C /* PearlSCrypt.m in Sources */,
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */, DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */,
DA2CA4F118D323D3007798F8 /* NSTimer+PearlBlock.m in Sources */,
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */, DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */,
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */, DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */,
DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */,
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */, DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */,
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */, DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */,
DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */, DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */,
@@ -1578,7 +1918,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@@ -1644,7 +1984,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements; CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist"; INFOPLIST_FILE = "MasterPassword-Info.plist";
@@ -1659,7 +1998,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements; CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist"; INFOPLIST_FILE = "MasterPassword-Info.plist";
@@ -1689,7 +2027,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@@ -1755,7 +2093,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements; CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch"; GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist"; INFOPLIST_FILE = "MasterPassword-Info.plist";

View File

@@ -4,11 +4,11 @@
<dict> <dict>
<key>com.apple.developer.ubiquity-container-identifiers</key> <key>com.apple.developer.ubiquity-container-identifiers</key>
<array> <array>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.Mac</string> <string>HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.Mac</string>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string> <string>HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared</string>
</array> </array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key> <key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string> <string>HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared</string>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
</dict> </dict>

View File

@@ -1,780 +1,204 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13B3116" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<data> <dependencies>
<int key="IBDocument.SystemTarget">1070</int> <deployment version="1070" defaultVersion="1080" identifier="macosx"/>
<string key="IBDocument.SystemVersion">12D78</string> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/>
<string key="IBDocument.InterfaceBuilderVersion">3084</string> </dependencies>
<string key="IBDocument.AppKitVersion">1187.37</string> <objects>
<string key="IBDocument.HIToolboxVersion">626.00</string> <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<object class="NSMutableDictionary" key="IBDocument.PluginVersions"> <connections>
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string> <outlet property="delegate" destination="494" id="495"/>
<string key="NS.object.0">3084</string> </connections>
</object> </customObject>
<array key="IBDocument.IntegratedClassDependencies"> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<string>NSCustomObject</string> <customObject id="-3" userLabel="Application"/>
<string>NSMenu</string> <menu title="AMainMenu" systemMenu="main" id="29"/>
<string>NSMenuItem</string> <customObject id="494" customClass="MPMacAppDelegate">
<string>NSUserDefaultsController</string> <connections>
</array> <outlet property="createUserItem" destination="757" id="763"/>
<array key="IBDocument.PluginDependencies"> <outlet property="dialogStyleHUD" destination="768" id="771"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <outlet property="dialogStyleRegular" destination="767" id="772"/>
</array> <outlet property="lockItem" destination="720" id="726"/>
<object class="NSMutableDictionary" key="IBDocument.Metadata"> <outlet property="openAtLoginItem" destination="785" id="788"/>
<string key="NS.key.0">PluginDependencyRecalculationVersion</string> <outlet property="rememberPasswordItem" destination="744" id="750"/>
<integer value="1" key="NS.object.0"/> <outlet property="savePasswordItem" destination="747" id="751"/>
</object> <outlet property="showItem" destination="719" id="783"/>
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1048"> <outlet property="statusMenu" destination="716" id="731"/>
<object class="NSCustomObject" id="1021"> <outlet property="useCloudItem" destination="743" id="749"/>
<string key="NSClassName">NSApplication</string> <outlet property="usersItem" destination="755" id="762"/>
</object> </connections>
<object class="NSCustomObject" id="1014"> </customObject>
<string key="NSClassName">FirstResponder</string> <userDefaultsController representsSharedInstance="YES" id="548"/>
</object> <menu autoenablesItems="NO" id="716">
<object class="NSCustomObject" id="1050"> <items>
<string key="NSClassName">NSApplication</string> <menuItem title="Users" id="755">
</object> <modifierMask key="keyEquivalentModifierMask"/>
<object class="NSMenu" id="649796088"> <menu key="submenu" title="Users" id="756">
<string key="NSTitle">AMainMenu</string> <items>
<array class="NSMutableArray" key="NSMenuItems"/> <menuItem title="New User" enabled="NO" toolTip="Creating users is not yet supported. Please use the iOS app with iCloud enabled to create users and sites." id="757">
<string key="NSName">_NSMainMenu</string> <modifierMask key="keyEquivalentModifierMask"/>
</object> <connections>
<object class="NSCustomObject" id="976324537"> <action selector="newUser:" target="494" id="761"/>
<string key="NSClassName">MPMacAppDelegate</string> </connections>
</object> </menuItem>
<object class="NSUserDefaultsController" id="705910970"> <menuItem isSeparatorItem="YES" id="759"/>
<bool key="NSSharedInstance">YES</bool> </items>
</object> </menu>
<object class="NSMenu" id="764588027"> </menuItem>
<string key="NSTitle"/> <menuItem title="Preferences" id="739">
<array class="NSMutableArray" key="NSMenuItems"> <modifierMask key="keyEquivalentModifierMask"/>
<object class="NSMenuItem" id="11982480"> <menu key="submenu" title="Preferences" autoenablesItems="NO" id="742">
<reference key="NSMenu" ref="764588027"/> <items>
<string key="NSTitle">Users</string> <menuItem title="Use iCloud" id="743">
<string key="NSKeyEquiv"/> <modifierMask key="keyEquivalentModifierMask"/>
<int key="NSMnemonicLoc">2147483647</int> <connections>
<object class="NSCustomResource" key="NSOnImage" id="269450960"> <action selector="togglePreference:" target="494" id="752"/>
<string key="NSClassName">NSImage</string> </connections>
<string key="NSResourceName">NSMenuCheckmark</string> </menuItem>
</object> <menuItem title="Synchronize available sites from your iCloud account." enabled="NO" id="746">
<object class="NSCustomResource" key="NSMixedImage" id="977440657"> <attributedString key="attributedTitle">
<string key="NSClassName">NSImage</string> <fragment content="Synchronize available sites from your iCloud account.">
<string key="NSResourceName">NSMenuMixedState</string> <attributes>
</object> <font key="NSFont" size="12" name="Helvetica"/>
<string key="NSAction">submenuAction:</string> <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
<object class="NSMenu" key="NSSubmenu" id="934187555"> </attributes>
<string key="NSTitle">Users</string> </fragment>
<array class="NSMutableArray" key="NSMenuItems"> </attributedString>
<object class="NSMenuItem" id="576787569"> <modifierMask key="keyEquivalentModifierMask"/>
<reference key="NSMenu" ref="934187555"/> </menuItem>
<bool key="NSIsDisabled">YES</bool> <menuItem title="Open At Login" id="785">
<string key="NSTitle">New User</string> <modifierMask key="keyEquivalentModifierMask"/>
<string key="NSKeyEquiv"/> <connections>
<int key="NSMnemonicLoc">2147483647</int> <action selector="togglePreference:" target="494" id="787"/>
<reference key="NSOnImage" ref="269450960"/> </connections>
<reference key="NSMixedImage" ref="977440657"/> </menuItem>
</object> <menuItem title="Always open Master Password at start-up." enabled="NO" id="786">
<object class="NSMenuItem" id="925131766"> <attributedString key="attributedTitle">
<reference key="NSMenu" ref="934187555"/> <fragment content="Always open Master Password at start-up.">
<bool key="NSIsDisabled">YES</bool> <attributes>
<bool key="NSIsSeparator">YES</bool> <font key="NSFont" size="12" name="Helvetica"/>
<string key="NSTitle"/> <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
<string key="NSKeyEquiv"/> </attributes>
<int key="NSMnemonicLoc">2147483647</int> </fragment>
<reference key="NSOnImage" ref="269450960"/> </attributedString>
<reference key="NSMixedImage" ref="977440657"/> <modifierMask key="keyEquivalentModifierMask"/>
</object> </menuItem>
</array> <menuItem title="Remember Password" id="744">
</object> <modifierMask key="keyEquivalentModifierMask"/>
</object> <connections>
<object class="NSMenuItem" id="851296005"> <action selector="togglePreference:" target="494" id="753"/>
<reference key="NSMenu" ref="764588027"/> </connections>
<string key="NSTitle">Preferences</string> </menuItem>
<string key="NSKeyEquiv"/> <menuItem title="Remember the password while the application is running." enabled="NO" id="745">
<int key="NSMnemonicLoc">2147483647</int> <attributedString key="attributedTitle">
<reference key="NSOnImage" ref="269450960"/> <fragment content="Remember the password while the application is running.">
<reference key="NSMixedImage" ref="977440657"/> <attributes>
<string key="NSAction">submenuAction:</string> <font key="NSFont" size="12" name="Helvetica"/>
<object class="NSMenu" key="NSSubmenu" id="800575174"> <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
<string key="NSTitle">Preferences</string> </attributes>
<array class="NSMutableArray" key="NSMenuItems"> </fragment>
<object class="NSMenuItem" id="14397049"> </attributedString>
<reference key="NSMenu" ref="800575174"/> <modifierMask key="keyEquivalentModifierMask"/>
<string key="NSTitle">Use iCloud</string> </menuItem>
<string key="NSKeyEquiv"/> <menuItem title="Save Password" id="747">
<int key="NSMnemonicLoc">2147483647</int> <modifierMask key="keyEquivalentModifierMask"/>
<reference key="NSOnImage" ref="269450960"/> <connections>
<reference key="NSMixedImage" ref="977440657"/> <action selector="togglePreference:" target="494" id="754"/>
</object> </connections>
<object class="NSMenuItem" id="461686112"> </menuItem>
<reference key="NSMenu" ref="800575174"/> <menuItem title="Save the password in your keychain so you don't need to enter it again." enabled="NO" id="748">
<bool key="NSIsDisabled">YES</bool> <attributedString key="attributedTitle">
<string key="NSTitle">Synchronize available sites from your iCloud account.</string> <fragment content="Save the password in your keychain so you don't need to enter it again.">
<string key="NSKeyEquiv"/> <attributes>
<int key="NSMnemonicLoc">2147483647</int> <font key="NSFont" size="12" name="Helvetica"/>
<reference key="NSOnImage" ref="269450960"/> <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
<reference key="NSMixedImage" ref="977440657"/> </attributes>
<object class="NSAttributedString" key="NSAttributedTitle"> </fragment>
<string key="NSString">Synchronize available sites from your iCloud account.</string> </attributedString>
<dictionary key="NSAttributes" id="583461090"> <modifierMask key="keyEquivalentModifierMask"/>
<object class="NSFont" key="NSFont"> </menuItem>
<string key="NSName">Helvetica</string> <menuItem title="Password Dialog Style" id="765">
<double key="NSSize">12</double> <modifierMask key="keyEquivalentModifierMask"/>
<int key="NSfFlags">16</int> <menu key="submenu" title="Password Dialog Style" id="766">
</object> <items>
<object class="NSParagraphStyle" key="NSParagraphStyle"> <menuItem title="Regular" id="767">
<int key="NSAlignment">4</int> <modifierMask key="keyEquivalentModifierMask"/>
<nil key="NSTabStops"/> <connections>
</object> <action selector="togglePreference:" target="494" id="773"/>
</dictionary> </connections>
</object> </menuItem>
</object> <menuItem title="HUD" tag="1" id="768">
<object class="NSMenuItem" id="362714849"> <modifierMask key="keyEquivalentModifierMask"/>
<reference key="NSMenu" ref="800575174"/> <connections>
<string key="NSTitle">Open At Login</string> <action selector="togglePreference:" target="494" id="774"/>
<string key="NSKeyEquiv"/> </connections>
<int key="NSMnemonicLoc">2147483647</int> </menuItem>
<reference key="NSOnImage" ref="269450960"/> </items>
<reference key="NSMixedImage" ref="977440657"/> </menu>
</object> </menuItem>
<object class="NSMenuItem" id="409463580"> <menuItem title="Advanced" id="776">
<reference key="NSMenu" ref="800575174"/> <modifierMask key="keyEquivalentModifierMask"/>
<bool key="NSIsDisabled">YES</bool> <menu key="submenu" title="Advanced" id="777">
<string key="NSTitle">Always open Master Password at start-up.</string> <items>
<string key="NSKeyEquiv"/> <menuItem title="iCloud Truth Push" id="778">
<int key="NSMnemonicLoc">2147483647</int> <modifierMask key="keyEquivalentModifierMask"/>
<reference key="NSOnImage" ref="269450960"/> <connections>
<reference key="NSMixedImage" ref="977440657"/> <action selector="rebuildCloud:" target="494" id="780"/>
<object class="NSAttributedString" key="NSAttributedTitle"> </connections>
<string key="NSString">Always open Master Password at start-up.</string> </menuItem>
<reference key="NSAttributes" ref="583461090"/> <menuItem title="Force our version of the truth upon all other devices." enabled="NO" id="779">
</object> <attributedString key="attributedTitle">
</object> <fragment content="Force this device's version of the truth upon all others.">
<object class="NSMenuItem" id="290760748"> <attributes>
<reference key="NSMenu" ref="800575174"/> <font key="NSFont" size="12" name="Helvetica"/>
<string key="NSTitle">Remember Password</string> <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
<string key="NSKeyEquiv"/> </attributes>
<int key="NSMnemonicLoc">2147483647</int> </fragment>
<reference key="NSOnImage" ref="269450960"/> </attributedString>
<reference key="NSMixedImage" ref="977440657"/> <modifierMask key="keyEquivalentModifierMask"/>
</object> </menuItem>
<object class="NSMenuItem" id="907921953"> <menuItem title="iCloud Truth Pull" id="cLQ-kc-cYN">
<reference key="NSMenu" ref="800575174"/> <modifierMask key="keyEquivalentModifierMask"/>
<bool key="NSIsDisabled">YES</bool> <connections>
<string key="NSTitle">Remember the password while the application is running.</string> <action selector="corruptCloud:" target="494" id="asr-sb-Zkz"/>
<string key="NSKeyEquiv"/> </connections>
<int key="NSMnemonicLoc">2147483647</int> </menuItem>
<reference key="NSOnImage" ref="269450960"/> <menuItem title="Mark ourselves as corrupt and pull the truth from another." enabled="NO" id="6NL-ki-Jff">
<reference key="NSMixedImage" ref="977440657"/> <attributedString key="attributedTitle">
<object class="NSAttributedString" key="NSAttributedTitle"> <fragment content="Force this device's version of the truth upon all others.">
<string key="NSString">Remember the password while the application is running.</string> <attributes>
<reference key="NSAttributes" ref="583461090"/> <font key="NSFont" size="12" name="Helvetica"/>
</object> <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</object> </attributes>
<object class="NSMenuItem" id="110488020"> </fragment>
<reference key="NSMenu" ref="800575174"/> </attributedString>
<string key="NSTitle">Save Password</string> <modifierMask key="keyEquivalentModifierMask"/>
<string key="NSKeyEquiv"/> </menuItem>
<int key="NSMnemonicLoc">2147483647</int> </items>
<reference key="NSOnImage" ref="269450960"/> </menu>
<reference key="NSMixedImage" ref="977440657"/> </menuItem>
</object> </items>
<object class="NSMenuItem" id="123831322"> </menu>
<reference key="NSMenu" ref="800575174"/> </menuItem>
<bool key="NSIsDisabled">YES</bool> <menuItem isSeparatorItem="YES" id="718"/>
<string key="NSTitle">Save the password in your keychain so you don't need to enter it again.</string> <menuItem title="Open" keyEquivalent="p" id="719">
<string key="NSKeyEquiv"/> <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<int key="NSMnemonicLoc">2147483647</int> <connections>
<reference key="NSOnImage" ref="269450960"/> <action selector="showPasswordWindow:" target="494" id="782"/>
<reference key="NSMixedImage" ref="977440657"/> </connections>
<object class="NSAttributedString" key="NSAttributedTitle"> </menuItem>
<string key="NSString">Save the password in your keychain so you don't need to enter it again.</string> <menuItem title="Lock" enabled="NO" keyEquivalent="p" id="720">
<reference key="NSAttributes" ref="583461090"/> <modifierMask key="keyEquivalentModifierMask" control="YES" option="YES" command="YES"/>
</object> <connections>
</object> <action selector="lock:" target="494" id="764"/>
<object class="NSMenuItem" id="123543264"> </connections>
<reference key="NSMenu" ref="800575174"/> </menuItem>
<string key="NSTitle">Password Dialog Style</string> <menuItem title="Quit" keyEquivalent="q" id="717">
<string key="NSKeyEquiv"/> <connections>
<int key="NSMnemonicLoc">2147483647</int> <action selector="terminate:" target="494" id="784"/>
<reference key="NSOnImage" ref="269450960"/> </connections>
<reference key="NSMixedImage" ref="977440657"/> </menuItem>
<string key="NSAction">submenuAction:</string> </items>
<object class="NSMenu" key="NSSubmenu" id="293904698"> </menu>
<string key="NSTitle">Password Dialog Style</string> </objects>
<array class="NSMutableArray" key="NSMenuItems"> </document>
<object class="NSMenuItem" id="560371092">
<reference key="NSMenu" ref="293904698"/>
<string key="NSTitle">Regular</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
</object>
<object class="NSMenuItem" id="117792016">
<reference key="NSMenu" ref="293904698"/>
<string key="NSTitle">HUD</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
<int key="NSTag">1</int>
</object>
</array>
</object>
</object>
<object class="NSMenuItem" id="939693094">
<reference key="NSMenu" ref="800575174"/>
<string key="NSTitle">Advanced</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
<string key="NSAction">submenuAction:</string>
<object class="NSMenu" key="NSSubmenu" id="534220172">
<string key="NSTitle">Advanced</string>
<array class="NSMutableArray" key="NSMenuItems">
<object class="NSMenuItem" id="842321178">
<reference key="NSMenu" ref="534220172"/>
<string key="NSTitle">iCloud Truth Sync</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
</object>
<object class="NSMenuItem" id="946018575">
<reference key="NSMenu" ref="534220172"/>
<bool key="NSIsDisabled">YES</bool>
<string key="NSTitle">Force this device's version of the truth upon all others.</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
<object class="NSAttributedString" key="NSAttributedTitle">
<string key="NSString">Force this device's version of the truth upon all others.</string>
<reference key="NSAttributes" ref="583461090"/>
</object>
</object>
</array>
</object>
</object>
</array>
<bool key="NSNoAutoenable">YES</bool>
</object>
</object>
<object class="NSMenuItem" id="466252869">
<reference key="NSMenu" ref="764588027"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
</object>
<object class="NSMenuItem" id="846612332">
<reference key="NSMenu" ref="764588027"/>
<string key="NSTitle">Show</string>
<string key="NSKeyEquiv">p</string>
<int key="NSKeyEquivModMask">1310720</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
</object>
<object class="NSMenuItem" id="229948989">
<reference key="NSMenu" ref="764588027"/>
<bool key="NSIsDisabled">YES</bool>
<string key="NSTitle">Lock</string>
<string key="NSKeyEquiv">p</string>
<int key="NSKeyEquivModMask">1835008</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
</object>
<object class="NSMenuItem" id="291035877">
<reference key="NSMenu" ref="764588027"/>
<string key="NSTitle">Quit</string>
<string key="NSKeyEquiv">q</string>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="269450960"/>
<reference key="NSMixedImage" ref="977440657"/>
</object>
</array>
<bool key="NSNoAutoenable">YES</bool>
</object>
</array>
<object class="IBObjectContainer" key="IBDocument.Objects">
<array class="NSMutableArray" key="connectionRecords">
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1021"/>
<reference key="destination" ref="976324537"/>
</object>
<int key="connectionID">495</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">lockItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="229948989"/>
</object>
<int key="connectionID">726</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">statusMenu</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="764588027"/>
</object>
<int key="connectionID">731</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">useCloudItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="14397049"/>
</object>
<int key="connectionID">749</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">rememberPasswordItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="290760748"/>
</object>
<int key="connectionID">750</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">savePasswordItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="110488020"/>
</object>
<int key="connectionID">751</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="14397049"/>
</object>
<int key="connectionID">752</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="290760748"/>
</object>
<int key="connectionID">753</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="110488020"/>
</object>
<int key="connectionID">754</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">newUser:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="576787569"/>
</object>
<int key="connectionID">761</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">usersItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="11982480"/>
</object>
<int key="connectionID">762</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">createUserItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="576787569"/>
</object>
<int key="connectionID">763</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">lock:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="229948989"/>
</object>
<int key="connectionID">764</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">dialogStyleHUD</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="117792016"/>
</object>
<int key="connectionID">771</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">dialogStyleRegular</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="560371092"/>
</object>
<int key="connectionID">772</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="560371092"/>
</object>
<int key="connectionID">773</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="117792016"/>
</object>
<int key="connectionID">774</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">rebuildCloud:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="842321178"/>
</object>
<int key="connectionID">780</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">showPasswordWindow:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="846612332"/>
</object>
<int key="connectionID">782</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">showItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="846612332"/>
</object>
<int key="connectionID">783</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">terminate:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="291035877"/>
</object>
<int key="connectionID">784</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">togglePreference:</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="362714849"/>
</object>
<int key="connectionID">787</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">openAtLoginItem</string>
<reference key="source" ref="976324537"/>
<reference key="destination" ref="362714849"/>
</object>
<int key="connectionID">788</int>
</object>
</array>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
<object class="IBObjectRecord">
<int key="objectID">0</int>
<array key="object" id="0"/>
<reference key="children" ref="1048"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1021"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1014"/>
<reference key="parent" ref="0"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1050"/>
<reference key="parent" ref="0"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">29</int>
<reference key="object" ref="649796088"/>
<array class="NSMutableArray" key="children"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">494</int>
<reference key="object" ref="976324537"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">548</int>
<reference key="object" ref="705910970"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">716</int>
<reference key="object" ref="764588027"/>
<array class="NSMutableArray" key="children">
<reference ref="291035877"/>
<reference ref="466252869"/>
<reference ref="229948989"/>
<reference ref="851296005"/>
<reference ref="846612332"/>
<reference ref="11982480"/>
</array>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">717</int>
<reference key="object" ref="291035877"/>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">718</int>
<reference key="object" ref="466252869"/>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">719</int>
<reference key="object" ref="846612332"/>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">720</int>
<reference key="object" ref="229948989"/>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">739</int>
<reference key="object" ref="851296005"/>
<array class="NSMutableArray" key="children">
<reference ref="800575174"/>
</array>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">742</int>
<reference key="object" ref="800575174"/>
<array class="NSMutableArray" key="children">
<reference ref="14397049"/>
<reference ref="290760748"/>
<reference ref="907921953"/>
<reference ref="461686112"/>
<reference ref="110488020"/>
<reference ref="123831322"/>
<reference ref="123543264"/>
<reference ref="939693094"/>
<reference ref="409463580"/>
<reference ref="362714849"/>
</array>
<reference key="parent" ref="851296005"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">743</int>
<reference key="object" ref="14397049"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">745</int>
<reference key="object" ref="907921953"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">744</int>
<reference key="object" ref="290760748"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">746</int>
<reference key="object" ref="461686112"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">747</int>
<reference key="object" ref="110488020"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">748</int>
<reference key="object" ref="123831322"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">755</int>
<reference key="object" ref="11982480"/>
<array class="NSMutableArray" key="children">
<reference ref="934187555"/>
</array>
<reference key="parent" ref="764588027"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">756</int>
<reference key="object" ref="934187555"/>
<array class="NSMutableArray" key="children">
<reference ref="576787569"/>
<reference ref="925131766"/>
</array>
<reference key="parent" ref="11982480"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">757</int>
<reference key="object" ref="576787569"/>
<reference key="parent" ref="934187555"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">759</int>
<reference key="object" ref="925131766"/>
<reference key="parent" ref="934187555"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">765</int>
<reference key="object" ref="123543264"/>
<array class="NSMutableArray" key="children">
<reference ref="293904698"/>
</array>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">766</int>
<reference key="object" ref="293904698"/>
<array class="NSMutableArray" key="children">
<reference ref="560371092"/>
<reference ref="117792016"/>
</array>
<reference key="parent" ref="123543264"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">767</int>
<reference key="object" ref="560371092"/>
<reference key="parent" ref="293904698"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">768</int>
<reference key="object" ref="117792016"/>
<reference key="parent" ref="293904698"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">776</int>
<reference key="object" ref="939693094"/>
<array class="NSMutableArray" key="children">
<reference ref="534220172"/>
</array>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">777</int>
<reference key="object" ref="534220172"/>
<array class="NSMutableArray" key="children">
<reference ref="842321178"/>
<reference ref="946018575"/>
</array>
<reference key="parent" ref="939693094"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">778</int>
<reference key="object" ref="842321178"/>
<reference key="parent" ref="534220172"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">779</int>
<reference key="object" ref="946018575"/>
<reference key="parent" ref="534220172"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">785</int>
<reference key="object" ref="362714849"/>
<reference key="parent" ref="800575174"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">786</int>
<reference key="object" ref="409463580"/>
<reference key="parent" ref="800575174"/>
</object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="494.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="548.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="716.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="717.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="718.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="719.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="720.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="739.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="742.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="743.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="744.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="745.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="746.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="747.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="748.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="755.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="756.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSMutableDictionary" key="757.IBAttributePlaceholdersKey">
<string key="NS.key.0">ToolTip</string>
<object class="IBToolTipAttribute" key="NS.object.0">
<string key="name">ToolTip</string>
<reference key="object" ref="576787569"/>
<string key="toolTip">Creating users is not yet supported. Please use the iOS app with iCloud enabled to create users and sites.</string>
</object>
</object>
<string key="757.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="759.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="765.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="766.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="767.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="768.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="776.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="777.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="778.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="779.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="785.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="786.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">788</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes"/>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
<integer value="1070" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
<string key="NSMenuCheckmark">{11, 11}</string>
<string key="NSMenuMixedState">{10, 3}</string>
</dictionary>
<bool key="IBDocument.UseAutolayout">YES</bool>
</data>
</archive>

View File

@@ -0,0 +1,23 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAppSettingsViewController.h
// MPAppSettingsViewController
//
// Created by lhunath on 2014-04-18.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IASKAppSettingsViewController.h"
@interface MPAppSettingsViewController : IASKAppSettingsViewController
@end

View File

@@ -0,0 +1,63 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAppSettingsViewController.h
// MPAppSettingsViewController
//
// Created by lhunath on 2014-04-18.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPAppSettingsViewController.h"
#import "UIColor+Expanded.h"
@interface MPTableView:UITableView
@end
@implementation MPTableView
- (void)layoutSubviews {
[super layoutSubviews];
}
- (void)setContentInset:(UIEdgeInsets)contentInset {
[super setContentInset:contentInset];
}
@end
@implementation MPAppSettingsViewController {
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
cell.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor];
if (cell.selectionStyle != UITableViewCellSelectionStyleNone) {
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRGBAHex:0x78DDFB33];
}
return cell;
}
@end

View File

@@ -0,0 +1,47 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAvatarCell.h
// MPAvatarCell
//
// Created by lhunath on 2014-03-11.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPEntities.h"
@class MPAvatarCell;
/* Avatar with a "+" symbol. */
extern const long MPAvatarAdd;
typedef NS_ENUM(NSUInteger, MPAvatarMode) {
MPAvatarModeLowered,
MPAvatarModeRaisedButInactive,
MPAvatarModeRaisedAndActive,
MPAvatarModeRaisedAndHidden,
MPAvatarModeRaisedAndMinimized,
};
@interface MPAvatarCell : UICollectionViewCell
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) long avatar;
@property (assign, nonatomic) MPAvatarMode mode;
@property (assign, nonatomic) CGFloat visibility;
@property (assign, nonatomic) BOOL spinnerActive;
@property (assign, nonatomic, readonly) BOOL newUser;
+ (NSString *)reuseIdentifier;
- (void)setVisibility:(CGFloat)visibility animated:(BOOL)animated;
- (void)setMode:(MPAvatarMode)mode animated:(BOOL)animated;
@end

View File

@@ -0,0 +1,287 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAvatarCell.h
// MPAvatarCell
//
// Created by lhunath on 2014-03-11.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPAvatarCell.h"
#import "MPPasswordLargeCell.h"
const long MPAvatarAdd = 10000;
@interface MPAvatarCell()
@property(strong, nonatomic) IBOutlet UIImageView *avatarImageView;
@property(strong, nonatomic) IBOutlet UILabel *nameLabel;
@property(strong, nonatomic) IBOutlet UIView *nameContainer;
@property(strong, nonatomic) IBOutlet UIImageView *spinner;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *nameToCenterConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarSizeConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarToTopConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarRaisedConstraint;
@end
@implementation MPAvatarCell {
CAAnimationGroup *_targetedShadowAnimation;
}
+ (NSString *)reuseIdentifier {
return NSStringFromClass( self );
}
#pragma mark - Life cycle
- (void)awakeFromNib {
[super awakeFromNib];
self.alpha = 0;
self.nameContainer.layer.cornerRadius = 5;
self.avatarImageView.hidden = NO;
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
self.avatarImageView.layer.masksToBounds = NO;
self.avatarImageView.backgroundColor = [UIColor clearColor];
[self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
[_self updateAnimated:YES];
}];
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
[_self updateAnimated:YES];
}];
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
toShadowOpacityAnimation.toValue = @0.2f;
toShadowOpacityAnimation.duration = 0.5f;
CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
pulseShadowOpacityAnimation.fromValue = @0.2f;
pulseShadowOpacityAnimation.toValue = @0.6f;
pulseShadowOpacityAnimation.beginTime = 0.5f;
pulseShadowOpacityAnimation.duration = 2.0f;
pulseShadowOpacityAnimation.autoreverses = YES;
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
_targetedShadowAnimation = [CAAnimationGroup new];
_targetedShadowAnimation.animations = @[ toShadowOpacityAnimation, pulseShadowOpacityAnimation ];
_targetedShadowAnimation.duration = MAXFLOAT;
self.avatarImageView.layer.shadowColor = [UIColor whiteColor].CGColor;
self.avatarImageView.layer.shadowOffset = CGSizeZero;
}
- (void)prepareForReuse {
[super prepareForReuse];
_newUser = NO;
[self setVisibility:0 animated:NO];
[self setMode:MPAvatarModeLowered animated:NO];
[self setSpinnerActive:NO animated:NO];
}
- (void)dealloc {
[self removeKeyPathObservers];
}
#pragma mark - Properties
- (void)setAvatar:(long)avatar {
_avatar = avatar == MPAvatarAdd? MPAvatarAdd: (avatar + MPAvatarCount) % MPAvatarCount;
if (_avatar == MPAvatarAdd) {
self.avatarImageView.image = [UIImage imageNamed:@"avatar-add"];
self.name = strl( @"New User" );
_newUser = YES;
}
else
self.avatarImageView.image = [UIImage imageNamed:strf( @"avatar-%ld", _avatar )];
}
- (NSString *)name {
return self.nameLabel.text;
}
- (void)setName:(NSString *)name {
self.nameLabel.text = name;
}
- (void)setVisibility:(CGFloat)visibility {
[self setVisibility:visibility animated:YES];
}
- (void)setVisibility:(CGFloat)visibility animated:(BOOL)animated {
_visibility = visibility;
[self updateAnimated:animated];
}
- (void)setHighlighted:(BOOL)highlighted {
super.highlighted = highlighted;
[UIView animateWithDuration:0.1f animations:^{
self.avatarImageView.transform = highlighted? CGAffineTransformMakeScale( 1.1f, 1.1f ): CGAffineTransformIdentity;
}];
}
- (void)setMode:(MPAvatarMode)mode {
[self setMode:mode animated:YES];
}
- (void)setMode:(MPAvatarMode)mode animated:(BOOL)animated {
_mode = mode;
[self updateAnimated:animated];
}
- (void)setSpinnerActive:(BOOL)spinnerActive {
[self setSpinnerActive:spinnerActive animated:YES];
}
- (void)setSpinnerActive:(BOOL)spinnerActive animated:(BOOL)animated {
if (_spinnerActive == spinnerActive)
return;
_spinnerActive = spinnerActive;
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.toValue = [NSNumber numberWithDouble:2 * M_PI];
rotate.duration = 5.0;
if (spinnerActive) {
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
rotate.fromValue = @0.0;
rotate.repeatCount = MAXFLOAT;
}
else {
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
rotate.repeatCount = 1;
}
[self.spinner.layer removeAnimationForKey:@"rotation"];
[self.spinner.layer addAnimation:rotate forKey:@"rotation"];
[self updateAnimated:animated];
}
#pragma mark - Private
- (void)updateAnimated:(BOOL)animated {
[UIView animateWithDuration:animated? 0.2f: 0 animations:^{
self.avatarImageView.transform = CGAffineTransformIdentity;
}];
[UIView animateWithDuration:animated? 0.3f: 0 delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{
self.alpha = 1;
if (self.newUser) {
if (self.mode == MPAvatarModeLowered)
self.avatar = MPAvatarAdd;
else if (self.avatar == MPAvatarAdd)
self.avatar = arc4random() % MPAvatarCount;
}
switch (self.mode) {
case MPAvatarModeLowered: {
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultLow;
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultLow;
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultLow;
self.nameContainer.alpha = self.visibility;
self.nameContainer.backgroundColor = [UIColor clearColor];
self.avatarImageView.alpha = self.visibility / 0.7f + 0.3f;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
break;
}
case MPAvatarModeRaisedButInactive: {
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultHigh;
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultLow;
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultLow;
self.nameContainer.alpha = self.visibility;
self.nameContainer.backgroundColor = [UIColor clearColor];
self.avatarImageView.alpha = 0;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
break;
}
case MPAvatarModeRaisedAndActive: {
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultHigh;
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultLow;
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultHigh;
self.nameContainer.alpha = self.visibility;
self.nameContainer.backgroundColor = [UIColor blackColor];
self.avatarImageView.alpha = 1;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
break;
}
case MPAvatarModeRaisedAndHidden: {
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultHigh;
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultLow;
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultHigh;
self.nameContainer.alpha = 0;
self.nameContainer.backgroundColor = [UIColor blackColor];
self.avatarImageView.alpha = 0;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
break;
}
case MPAvatarModeRaisedAndMinimized: {
self.avatarSizeConstraint.constant = 36;
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultLow;
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultHigh;
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultHigh;
self.nameContainer.alpha = 0;
self.nameContainer.backgroundColor = [UIColor blackColor];
self.avatarImageView.alpha = 1;
break;
}
}
[self.avatarSizeConstraint apply];
[self.avatarRaisedConstraint apply];
[self.avatarToTopConstraint apply];
[self.nameToCenterConstraint apply];
// Avatar minimized.
if (self.mode == MPAvatarModeRaisedAndMinimized)
[self.avatarImageView.layer removeAllAnimations];
else if (![self.avatarImageView.layer animationForKey:@"targetedShadow"])
[self.avatarImageView.layer addAnimation:_targetedShadowAnimation forKey:@"targetedShadow"];
// Avatar selection and spinner.
if (self.mode != MPAvatarModeRaisedAndMinimized && (self.selected || self.highlighted) && !self.spinnerActive)
self.avatarImageView.backgroundColor = self.avatarImageView.tintColor;
else
self.avatarImageView.backgroundColor = [UIColor clearColor];
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
self.spinner.alpha = self.spinnerActive? 1: 0;
} completion:nil];
}
@end

View File

@@ -0,0 +1,23 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCell.h
// MPCell
//
// Created by lhunath on 2014-03-27.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MPCell : UICollectionViewCell
@end

View File

@@ -0,0 +1,24 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCell.h
// MPCell
//
// Created by lhunath on 2014-03-27.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPCell.h"
@implementation MPCell {
}
@end

View File

@@ -0,0 +1,37 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCoachmarkViewController.h
// MPCoachmarkViewController
//
// Created by lhunath on 2014-04-22.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MPCoachmark : NSObject
@property(nonatomic, strong) Class coachedClass;
@property(nonatomic) int coachedVersion;
@property(nonatomic) BOOL coached;
+ (instancetype)coachmarkForClass:(Class)class version:(NSInteger)version;
@end
@interface MPCoachmarkViewController : UIViewController
@property(nonatomic, strong) MPCoachmark *coachmark;
- (IBAction)close:(id)sender;
@end

View File

@@ -0,0 +1,53 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCoachmarkViewController.h
// MPCoachmarkViewController
//
// Created by lhunath on 2014-04-22.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPCoachmarkViewController.h"
@implementation MPCoachmarkViewController {
}
- (IBAction)close:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{
self.coachmark.coached = YES;
}];
}
@end
@implementation MPCoachmark
+ (instancetype)coachmarkForClass:(Class)coachedClass version:(NSInteger)coachedVersion {
MPCoachmark *coachmark = [self new];
coachmark.coachedClass = coachedClass;
coachmark.coachedVersion = coachedVersion;
return coachmark;
}
- (BOOL)coached {
return [[NSUserDefaults standardUserDefaults] boolForKey:strf( @"%@.%d.coached", self.coachedClass, self.coachedVersion )];
}
- (void)setCoached:(BOOL)coached {
[[NSUserDefaults standardUserDefaults] setBool:coached forKey:strf( @"%@.%d.coached", self.coachedClass, self.coachedVersion )];
}
@end

View File

@@ -0,0 +1,29 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCombinedViewController.h
// MPCombinedViewController
//
// Created by lhunath on 2014-03-08.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
typedef NS_ENUM(NSUInteger, MPCombinedMode) {
MPCombinedModeUserSelection,
MPCombinedModePasswordSelection,
};
@interface MPCombinedViewController : UIViewController
@property(assign, nonatomic) MPCombinedMode mode;
@property(strong, nonatomic) IBOutlet UIView *usersView;
@end

View File

@@ -0,0 +1,187 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCombinedViewController.h
// MPCombinedViewController
//
// Created by lhunath on 2014-03-08.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPCombinedViewController.h"
#import "MPUsersViewController.h"
#import "MPPasswordsViewController.h"
#import "MPEmergencySegue.h"
#import "MPEmergencyViewController.h"
#import "MPPasswordsSegue.h"
@interface MPCombinedViewController()
@property(nonatomic, weak) MPUsersViewController *usersVC;
@property(nonatomic, weak) MPEmergencyViewController *emergencyVC;
@end
@implementation MPCombinedViewController {
NSArray *_notificationObservers;
MPPasswordsViewController *_passwordsVC;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setMode:MPCombinedModeUserSelection animated:NO];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[self navigationController] setNavigationBarHidden:YES animated:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self registerObservers];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self removeObservers];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"users"])
self.usersVC = segue.destinationViewController;
if ([segue.identifier isEqualToString:@"passwords"]) {
NSAssert([segue isKindOfClass:[MPPasswordsSegue class]], @"passwords segue should be MPPasswordsSegue: %@", segue);
NSAssert([sender isKindOfClass:[NSDictionary class]], @"sender should be dictionary: %@", sender);
NSAssert([[sender objectForKey:@"animated"] isKindOfClass:[NSNumber class]], @"sender should contain 'animated': %@", sender);
[(MPPasswordsSegue *)segue setAnimated:[sender[@"animated"] boolValue]];
UIViewController *destinationVC = segue.destinationViewController;
_passwordsVC = [destinationVC isKindOfClass:[MPPasswordsViewController class]]? (MPPasswordsViewController *)destinationVC: nil;
}
if ([segue.identifier isEqualToString:@"emergency"])
self.emergencyVC = segue.destinationViewController;
}
- (BOOL)prefersStatusBarHidden {
return self.mode == MPCombinedModeUserSelection;
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (motion == UIEventSubtypeMotionShake && !self.emergencyVC)
[self performSegueWithIdentifier:@"emergency" sender:self];
}
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
if ([identifier isEqualToString:@"unwind-emergency"]) {
MPEmergencySegue *segue = [[MPEmergencySegue alloc] initWithIdentifier:identifier
source:fromViewController destination:toViewController];
segue.unwind = YES;
dbg_return(segue);
}
dbg_return((id)nil);
}
#pragma mark - Properties
- (void)setMode:(MPCombinedMode)mode {
[self setMode:mode animated:YES];
}
- (void)setMode:(MPCombinedMode)mode animated:(BOOL)animated {
if (_mode == mode && animated)
return;
_mode = mode;
[self setNeedsStatusBarAppearanceUpdate];
[self becomeFirstResponder];
[self.usersVC setNeedsStatusBarAppearanceUpdate];
[self.usersVC.view setNeedsUpdateConstraints];
[self.usersVC.view setNeedsLayout];
switch (self.mode) {
case MPCombinedModeUserSelection: {
self.usersView.userInteractionEnabled = YES;
[self.usersVC setActive:YES animated:animated];
if (_passwordsVC) {
MPPasswordsSegue *segue = [[MPPasswordsSegue alloc] initWithIdentifier:@"passwords" source:_passwordsVC destination:self];
[self prepareForSegue:segue sender:@{ @"animated" : @(animated) }];
[segue perform];
}
break;
}
case MPCombinedModePasswordSelection: {
self.usersView.userInteractionEnabled = NO;
[self.usersVC setActive:NO animated:animated];
[self performSegueWithIdentifier:@"passwords" sender:@{ @"animated" : @(animated) }];
break;
}
}
}
#pragma mark - Private
- (void)registerObservers {
if ([_notificationObservers count])
return;
Weakify(self);
_notificationObservers = @[
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedInNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
[self setMode:MPCombinedModePasswordSelection];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
[self setMode:MPCombinedModeUserSelection animated:[note.userInfo[@"animated"] boolValue]];
}],
];
}
- (void)removeObservers {
for (id observer in _notificationObservers)
[[NSNotificationCenter defaultCenter] removeObserver:observer];
_notificationObservers = nil;
}
@end

View File

@@ -66,10 +66,11 @@
__weak MPElementListAllViewController *wSelf = self; __weak MPElementListAllViewController *wSelf = self;
[[MPiOSAppDelegate get] addElementNamed:[alert textFieldAtIndex:0].text completion:^(MPElementEntity *element) { [[MPiOSAppDelegate get] addElementNamed:[alert textFieldAtIndex:0].text completion:^(MPElementEntity *element) {
if (element) { if (element)
[wSelf.delegate didSelectElement:element]; PearlMainQueue( ^{
[wSelf close:nil]; [wSelf.delegate didSelectElement:element];
} [wSelf close:nil];
} );
}]; }];
} }
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonOkay, nil]; cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonOkay, nil];
@@ -85,7 +86,7 @@
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
PearlOverlay *activity = [PearlOverlay showOverlayWithTitle:@"Upgrading Sites"]; PearlOverlay *activity = [PearlOverlay showProgressOverlayWithTitle:@"Upgrading Sites"];
[self performUpgradeAllWithCompletion:^(BOOL success, NSDictionary *changes) { [self performUpgradeAllWithCompletion:^(BOOL success, NSDictionary *changes) {
dispatch_async( dispatch_get_main_queue(), ^{ dispatch_async( dispatch_get_main_queue(), ^{
[self showUpgradeChanges:changes]; [self showUpgradeChanges:changes];
@@ -149,7 +150,7 @@
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
[PearlEMail sendEMailTo:nil subject:@"[Master Password] Upgrade Changes" body:formattedChanges]; [PearlEMail sendEMailTo:nil fromVC:self subject:@"[Master Password] Upgrade Changes" body:formattedChanges];
} cancelTitle:@"Don't Email" otherTitles:@"Send Email", nil]; } cancelTitle:@"Don't Email" otherTitles:@"Send Email", nil];
} }

View File

@@ -105,7 +105,7 @@
UISearchBar *searchBar = self.searchDisplayController.searchBar; UISearchBar *searchBar = self.searchDisplayController.searchBar;
CGRect searchBarFrame = searchBar.frame; CGRect searchBarFrame = searchBar.frame;
[searchBar.superview enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [searchBar.superview enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if ([subview isKindOfClass:[UIControl class]] && if ([subview isKindOfClass:[UIControl class]] &&
CGPointEqualToPoint( CGPointEqualToPoint(
@@ -118,7 +118,7 @@
*stop = YES; *stop = YES;
} }
} recurse:NO]; } recurse:NO];
} }
- (BOOL)newSiteSectionNeeded { - (BOOL)newSiteSectionNeeded {
@@ -193,7 +193,7 @@
NSString *query = [self.searchDisplayController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSString *query = [self.searchDisplayController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
cell.textLabel.text = query; cell.textLabel.text = query;
cell.detailTextLabel.text = PearlString( @"New site: %@", cell.detailTextLabel.text = PearlString( @"New site: %@",
[MPAlgorithmDefault shortNameOfType:[[[MPiOSAppDelegate get] activeUserForMainThread] defaultType]] ); [MPAlgorithmDefault shortNameOfType:[[MPiOSAppDelegate get] activeUserForMainThread].defaultType] );
} }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
@@ -220,7 +220,9 @@
__weak MPElementListController *wSelf = self; __weak MPElementListController *wSelf = self;
[[MPiOSAppDelegate get] addElementNamed:siteName completion:^(MPElementEntity *element) { [[MPiOSAppDelegate get] addElementNamed:siteName completion:^(MPElementEntity *element) {
if (element) if (element)
[wSelf.delegate didSelectElement:element]; PearlMainQueue( ^{
[wSelf.delegate didSelectElement:element];
} );
}]; }];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
} }

View File

@@ -0,0 +1,24 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPEmergencySegue.h
// MPEmergencySegue
//
// Created by lhunath on 2014-04-09.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MPEmergencySegue : UIStoryboardSegue
@property(nonatomic) BOOL unwind;
@end

View File

@@ -0,0 +1,57 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPEmergencySegue.h
// MPEmergencySegue
//
// Created by lhunath on 2014-04-09.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPEmergencySegue.h"
@implementation MPEmergencySegue {
}
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
if (!self.unwind) {
// Winding
[sourceViewController addChildViewController:destinationViewController];
[sourceViewController.view addSubview:destinationViewController.view];
CGRectSetY(destinationViewController.view.bounds, sourceViewController.view.frame.size.height);
[UIView transitionWithView:sourceViewController.view duration:0.3f options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
CGRectSetY(destinationViewController.view.bounds, 0);
} completion:^(BOOL finished) {
if (finished)
[destinationViewController didMoveToParentViewController:sourceViewController];
}];
}
else {
// Unwinding
[sourceViewController willMoveToParentViewController:nil];
[UIView transitionWithView:sourceViewController.parentViewController.view duration:0.3f options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
CGRectSetY(sourceViewController.view.bounds, sourceViewController.parentViewController.view.frame.size.height);
} completion:^(BOOL finished) {
if (finished) {
[sourceViewController.view removeFromSuperview];
[sourceViewController removeFromParentViewController];
}
}];
}
}
@end

View File

@@ -0,0 +1,38 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCombinedViewController.h
// MPCombinedViewController
//
// Created by lhunath on 2014-03-08.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "LLGitTip.h"
@interface MPEmergencyViewController : UIViewController <UITextFieldDelegate>
@property(weak, nonatomic) IBOutlet UIView *dialogView;
@property(weak, nonatomic) IBOutlet UIView *containerView;
@property(weak, nonatomic) IBOutlet UITextField *userNameField;
@property(weak, nonatomic) IBOutlet UITextField *masterPasswordField;
@property(weak, nonatomic) IBOutlet UITextField *siteField;
@property(weak, nonatomic) IBOutlet UIStepper *counterStepper;
@property(weak, nonatomic) IBOutlet UISegmentedControl *typeControl;
@property(weak, nonatomic) IBOutlet UILabel *counterLabel;
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *activity;
@property(weak, nonatomic) IBOutlet UILabel *passwordLabel;
@property(weak, nonatomic) IBOutlet UIView *tipContainer;
- (IBAction)controlChanged:(UIControl *)control;
- (IBAction)copyPassword:(UITapGestureRecognizer *)recognizer;
@end

View File

@@ -0,0 +1,202 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCombinedViewController.h
// MPCombinedViewController
//
// Created by lhunath on 2014-03-08.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPEmergencyViewController.h"
#import "MPEntities.h"
@implementation MPEmergencyViewController {
MPKey *_key;
NSOperationQueue *_emergencyKeyQueue;
NSOperationQueue *_emergencyPasswordQueue;
NSArray *_notificationObservers;
}
- (void)viewDidLoad {
[super viewDidLoad];
[_emergencyKeyQueue = [NSOperationQueue new] setMaxConcurrentOperationCount:1];
[_emergencyPasswordQueue = [NSOperationQueue new] setMaxConcurrentOperationCount:1];
self.view.backgroundColor = [UIColor clearColor];
self.dialogView.layer.cornerRadius = 5;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self reset];
[self registerObservers];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self removeObservers];
[self reset];
}
- (BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
return [self respondsToSelector:action];
}
#pragma mark - Actions
- (IBAction)unwindToCombined:(UIStoryboardSegue *)sender {
dbg(@"unwindToCombined:%@", sender);
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
#pragma mark - Actions
- (IBAction)controlChanged:(UIControl *)control {
if (control == self.userNameField || control == self.masterPasswordField)
[self updateKey];
else
[self updatePassword];
}
- (IBAction)copyPassword:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
NSString *sitePassword = self.passwordLabel.text;
if ([sitePassword length]) {
[UIPasteboard generalPasteboard].string = sitePassword;
[UIView animateWithDuration:0.3f animations:^{
self.tipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished)
PearlMainQueueAfter( 3, ^{
self.tipContainer.alpha = 0;
} );
}];
}
}
}
#pragma mark - Private
- (void)updateKey {
NSString *userName = self.userNameField.text;
NSString *masterPassword = self.masterPasswordField.text;
self.passwordLabel.text = nil;
[self.activity startAnimating];
[_emergencyKeyQueue cancelAllOperations];
[_emergencyKeyQueue addOperationWithBlock:^{
if ([masterPassword length] && [userName length])
_key = [MPAlgorithmDefault keyForPassword:masterPassword ofUserNamed:userName];
else
_key = nil;
PearlMainQueue( ^{
[self updatePassword];
} );
}];
}
- (void)updatePassword {
NSString *siteName = self.siteField.text;
MPElementType siteType = [self siteType];
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
self.counterLabel.text = strf( @"%d", siteCounter );
self.passwordLabel.text = nil;
[self.activity startAnimating];
[_emergencyPasswordQueue cancelAllOperations];
[_emergencyPasswordQueue addOperationWithBlock:^{
NSString *sitePassword = nil;
if (_key && [siteName length])
sitePassword = [MPAlgorithmDefault generateContentNamed:siteName ofType:siteType withCounter:siteCounter usingKey:_key];
PearlMainQueue( ^{
[self.activity stopAnimating];
self.passwordLabel.text = sitePassword;
} );
}];
}
- (enum MPElementType)siteType {
switch (self.typeControl.selectedSegmentIndex) {
case 0:
return MPElementTypeGeneratedMaximum;
case 1:
return MPElementTypeGeneratedLong;
case 2:
return MPElementTypeGeneratedMedium;
case 3:
return MPElementTypeGeneratedBasic;
case 4:
return MPElementTypeGeneratedShort;
case 5:
return MPElementTypeGeneratedPIN;
default:
Throw(@"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex);
}
}
- (void)reset {
self.userNameField.text = nil;
self.masterPasswordField.text = nil;
self.siteField.text = nil;
self.counterStepper.value = 1;
self.typeControl.selectedSegmentIndex = 1;
[self updateKey];
}
- (void)registerObservers {
if ([_notificationObservers count])
return;
Weakify(self);
_notificationObservers = @[
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
[self performSegueWithIdentifier:@"unwind-emergency" sender:self];
}],
];
}
- (void)removeObservers {
for (id observer in _notificationObservers)
[[NSNotificationCenter defaultCenter] removeObserver:observer];
_notificationObservers = nil;
}
@end

View File

@@ -10,6 +10,7 @@
@interface MPGuideViewController : UIViewController<UIScrollViewDelegate> @interface MPGuideViewController : UIViewController<UIScrollViewDelegate>
@property(weak, nonatomic) IBOutlet UISearchBar *searchBar;
@property(weak, nonatomic) IBOutlet UIView *siteNameTip; @property(weak, nonatomic) IBOutlet UIView *siteNameTip;
@property(weak, nonatomic) IBOutlet UIView *contentTip; @property(weak, nonatomic) IBOutlet UIView *contentTip;
@property(weak, nonatomic) IBOutlet UILabel *contentTipText; @property(weak, nonatomic) IBOutlet UILabel *contentTipText;

View File

@@ -64,6 +64,7 @@
// Via setup // Via setup
self.smallPlayButton.hidden = YES; self.smallPlayButton.hidden = YES;
self.searchBar.text = nil;
self.siteNameTip.alpha = 0; self.siteNameTip.alpha = 0;
self.content.alpha = 0; self.content.alpha = 0;
self.content.frame = CGRectSetHeight( self.content.frame, 180 ); self.content.frame = CGRectSetHeight( self.content.frame, 180 );
@@ -81,6 +82,7 @@
// Via segue // Via segue
self.largePlayButton.hidden = YES; self.largePlayButton.hidden = YES;
self.searchBar.text = @"gmail.com";
self.siteNameTip.alpha = 1; self.siteNameTip.alpha = 1;
self.content.alpha = 1; self.content.alpha = 1;
self.content.frame = CGRectSetHeight( self.content.frame, 231 ); self.content.frame = CGRectSetHeight( self.content.frame, 231 );
@@ -147,6 +149,7 @@
if (self.currentTick < 5) { if (self.currentTick < 5) {
[UIView animateWithDuration:0.5 animations:^{ [UIView animateWithDuration:0.5 animations:^{
self.searchBar.text = nil;
self.siteNameTip.alpha = 1; self.siteNameTip.alpha = 1;
self.content.alpha = 0; self.content.alpha = 0;
self.content.frame = CGRectSetHeight( self.content.frame, 180 ); self.content.frame = CGRectSetHeight( self.content.frame, 180 );
@@ -159,6 +162,7 @@
} }
else if (self.currentTick < 10) { else if (self.currentTick < 10) {
[UIView animateWithDuration:0.5 animations:^{ [UIView animateWithDuration:0.5 animations:^{
self.searchBar.text = @"gmail.com";
self.siteNameTip.alpha = 0; self.siteNameTip.alpha = 0;
self.content.alpha = 1; self.content.alpha = 1;
self.contentTip.alpha = 1; self.contentTip.alpha = 1;
@@ -171,6 +175,7 @@
} }
else if (self.currentTick < 15) { else if (self.currentTick < 15) {
[UIView animateWithDuration:0.5 animations:^{ [UIView animateWithDuration:0.5 animations:^{
self.searchBar.text = @"gmail.com";
self.siteNameTip.alpha = 0; self.siteNameTip.alpha = 0;
self.content.alpha = 1; self.content.alpha = 1;
self.contentTip.alpha = 1; self.contentTip.alpha = 1;
@@ -185,6 +190,7 @@
} }
else if (self.currentTick < 20) { else if (self.currentTick < 20) {
[UIView animateWithDuration:0.5 animations:^{ [UIView animateWithDuration:0.5 animations:^{
self.searchBar.text = @"gmail.com";
self.siteNameTip.alpha = 0; self.siteNameTip.alpha = 0;
self.content.alpha = 1; self.content.alpha = 1;
self.content.frame = CGRectSetHeight( self.content.frame, 231 ); self.content.frame = CGRectSetHeight( self.content.frame, 231 );
@@ -199,6 +205,7 @@
} }
else if (self.currentTick < 25) { else if (self.currentTick < 25) {
[UIView animateWithDuration:0.5 animations:^{ [UIView animateWithDuration:0.5 animations:^{
self.searchBar.text = @"gmail.com";
self.siteNameTip.alpha = 0; self.siteNameTip.alpha = 0;
self.content.alpha = 1; self.content.alpha = 1;
self.contentTip.alpha = 0; self.contentTip.alpha = 0;
@@ -212,6 +219,7 @@
} }
else if (self.currentTick < 30) { else if (self.currentTick < 30) {
[UIView animateWithDuration:0.5 animations:^{ [UIView animateWithDuration:0.5 animations:^{
self.searchBar.text = @"gmail.com";
self.siteNameTip.alpha = 0; self.siteNameTip.alpha = 0;
self.content.alpha = 1; self.content.alpha = 1;
self.contentTip.alpha = 0; self.contentTip.alpha = 0;
@@ -230,6 +238,7 @@
self.currentTick = 0; self.currentTick = 0;
[UIView animateWithDuration:0.5 animations:^{ [UIView animateWithDuration:0.5 animations:^{
[self.smallPlayButton setImage:[UIImage imageNamed:@"icon_play"] forState:UIControlStateNormal]; [self.smallPlayButton setImage:[UIImage imageNamed:@"icon_play"] forState:UIControlStateNormal];
self.searchBar.text = @"gmail.com";
self.siteNameTip.alpha = 1; self.siteNameTip.alpha = 1;
self.content.alpha = 1; self.content.alpha = 1;
self.contentTip.alpha = 1; self.contentTip.alpha = 1;

View File

@@ -28,12 +28,12 @@
[super viewDidLoad]; [super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil [[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil
queue:nil usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
dispatch_async( dispatch_get_main_queue(), ^{ self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
} );
}]; }];
} }
@@ -41,6 +41,8 @@
[super viewWillAppear:animated]; [super viewWillAppear:animated];
self.logView.contentInset = UIEdgeInsetsMake( 64, 0, 93, 0 );
[self refresh:nil]; [self refresh:nil];
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0; self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
@@ -62,7 +64,7 @@
if (buttonIndex_ == alert.cancelButtonIndex) if (buttonIndex_ == alert.cancelButtonIndex)
return; return;
_switchCloudStoreProgress = [PearlOverlay showOverlayWithTitle:@"Enumerating Stores"]; _switchCloudStoreProgress = [PearlOverlay showProgressOverlayWithTitle:@"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]; [self switchCloudStore];
} ); } );

View File

@@ -715,11 +715,6 @@
[self setHelpHidden:NO animated:YES]; [self setHelpHidden:NO animated:YES];
break; break;
} }
case 1: {
inf(@"Action: Guide");
[[MPiOSAppDelegate get] showGuide];
break;
}
case 2: { case 2: {
inf(@"Action: Preferences"); inf(@"Action: Preferences");
[self performSegueWithIdentifier:@"MP_UserProfile" sender:self]; [self performSegueWithIdentifier:@"MP_UserProfile" sender:self];
@@ -776,7 +771,7 @@
@"If you continue, the password for this site will change. " @"If you continue, the password for this site will change. "
@"You will need to update your account's old password to the new one." @"You will need to update your account's old password to the new one."
do:^BOOL(MPElementEntity *activeElement, NSManagedObjectContext *context) { do:^BOOL(MPElementEntity *activeElement, NSManagedObjectContext *context) {
_activeElementOID = [[MPiOSAppDelegate get] changeElement:activeElement inContext:context _activeElementOID = [[MPiOSAppDelegate get] changeElement:activeElement saveInContext:context
toType:type].objectID; toType:type].objectID;
return YES; return YES;
}]; }];

View File

@@ -0,0 +1,34 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAvatarCell.h
// MPAvatarCell
//
// Created by lhunath on 2014-03-11.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPEntities.h"
#import "MPCell.h"
@interface MPPasswordCell : MPCell
@property(strong, nonatomic) IBOutlet UILabel *nameLabel;
@property(strong, nonatomic) IBOutlet UIButton *loginButton;
/** Populate our UI to reflect the current state. */
- (void)updateAnimated:(BOOL)animated;
- (void)reloadWithElement:(MPElementEntity *)mainElement;
- (void)reloadWithTransientSite:(NSString *)siteName;
@end

View File

@@ -0,0 +1,96 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAvatarCell.h
// MPAvatarCell
//
// Created by lhunath on 2014-03-11.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordCell.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
@implementation MPPasswordCell
#pragma mark - Life cycle
- (void)awakeFromNib {
[super awakeFromNib];
self.layer.cornerRadius = 5;
self.layer.shadowOffset = CGSizeZero;
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0;
self.layer.shadowColor = [UIColor whiteColor].CGColor;
}
- (void)prepareForReuse {
[super prepareForReuse];
[self updateAnimated:NO];
}
// Unblocks animations for all CALayer properties (eg. shadowOpacity)
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
id<CAAction> defaultAction = [super actionForLayer:layer forKey:event];
if (defaultAction == (id)[NSNull null] && [event isEqualToString:@"position"])
return defaultAction;
return NSNullToNil(defaultAction);
}
#pragma mark - Properties
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
[self updateAnimated:YES];
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
[self updateAnimated:YES];
}
#pragma mark - Private
- (void)updateAnimated:(BOOL)animated {
if (![NSThread isMainThread]) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateAnimated:animated];
}];
return;
}
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.layer.shadowOpacity = self.selected? 1: self.highlighted? 0.3f: 0;
}];
}
- (void)reloadWithElement:(MPElementEntity *)mainElement {
self.nameLabel.text = mainElement.name;
}
- (void)reloadWithTransientSite:(NSString *)siteName {
self.nameLabel.text = strl( @"%@ - Tap to create", siteName );
}
@end

View File

@@ -0,0 +1,31 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordElementCell.h
// MPPasswordElementCell
//
// Created by lhunath on 2014-04-03.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPPasswordCell.h"
@interface MPPasswordElementCell : MPPasswordCell
@property(nonatomic, copy) NSString *transientSite;
- (MPElementEntity *)mainElement;
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context;
- (void)setElement:(MPElementEntity *)element;
- (void)reloadData;
@end

View File

@@ -0,0 +1,95 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordElementCell.h
// MPPasswordElementCell
//
// Created by lhunath on 2014-04-03.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordElementCell.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
@implementation MPPasswordElementCell {
NSManagedObjectID *_elementOID;
}
- (void)prepareForReuse {
_elementOID = nil;
_transientSite = nil;
[super prepareForReuse];
}
- (void)setTransientSite:(NSString *)transientSite {
if ([_transientSite isEqualToString:transientSite])
return;
dbg(@"transientSite: %@ -> %@", _transientSite, transientSite);
_transientSite = transientSite;
_elementOID = nil;
[self updateAnimated:YES];
[self reloadData];
}
- (void)setElement:(MPElementEntity *)element {
NSManagedObjectID *newElementOID = element.objectID;
NSAssert(!newElementOID.isTemporaryID, @"Element doesn't have a permanent objectID: %@", element);
if ([_elementOID isEqual:newElementOID])
return;
dbg(@"element: %@ -> %@", _elementOID, newElementOID);
_transientSite = nil;
_elementOID = newElementOID;
[self updateAnimated:YES];
[self reloadData];
}
- (MPElementEntity *)mainElement {
return [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
}
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context {
if (!_elementOID)
return nil;
NSError *error = nil;
MPElementEntity *element = _elementOID? (MPElementEntity *)[context existingObjectWithID:_elementOID error:&error]: nil;
if (_elementOID && !element)
err(@"Failed to load element: %@", error);
return element;
}
- (void)reloadData {
if (self.transientSite)
PearlMainQueue( ^{
[self reloadWithTransientSite:self.transientSite];
} );
else
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
[self reloadWithElement:[self elementInContext:mainContext]];
}];
}
@end

View File

@@ -0,0 +1,44 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAvatarCell.h
// MPAvatarCell
//
// Created by lhunath on 2014-03-11.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPEntities.h"
#import "MPCell.h"
#import "MPPasswordCell.h"
typedef NS_ENUM (NSUInteger, MPContentFieldMode) {
MPContentFieldModePassword,
MPContentFieldModeUser,
};
@interface MPPasswordLargeCell : MPPasswordCell <UITextFieldDelegate>
@property(nonatomic) MPElementType type;
@property(nonatomic) MPContentFieldMode contentFieldMode;
@property(nonatomic, strong) IBOutlet UILabel *typeLabel;
@property(nonatomic, strong) IBOutlet UITextField *contentField;
@property(nonatomic, strong) IBOutlet UIButton *upgradeButton;
+ (instancetype)dequeueCellWithType:(MPElementType)type fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath;
- (void)resolveContentOfCellTypeForTransientSite:(NSString *)siteName usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock;
- (void)resolveContentOfCellTypeForElement:(MPElementEntity *)element usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock;
- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context;
@end

View File

@@ -0,0 +1,231 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAvatarCell.h
// MPAvatarCell
//
// Created by lhunath on 2014-03-11.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordLargeCell.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPPasswordLargeGeneratedCell.h"
#import "MPPasswordLargeStoredCell.h"
#import "MPPasswordTypesCell.h"
@implementation MPPasswordLargeCell
#pragma mark - Life
+ (instancetype)dequeueCellWithType:(MPElementType)type fromCollectionView:(UICollectionView *)collectionView
atIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier;
if (type & MPElementTypeClassGenerated)
reuseIdentifier = NSStringFromClass( [MPPasswordLargeGeneratedCell class] );
else if (type & MPElementTypeClassStored)
reuseIdentifier = NSStringFromClass( [MPPasswordLargeStoredCell class] );
else
Throw(@"Unexpected password type: %@", [MPAlgorithmDefault nameOfType:type]);
MPPasswordLargeCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
cell.type = type;
return cell;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self prepareForReuse];
}
- (void)prepareForReuse {
_contentFieldMode = 0;
self.contentField.text = nil;
[super prepareForReuse];
}
- (void)reloadWithTransientSite:(NSString *)siteName {
[super reloadWithTransientSite:siteName];
self.loginButton.alpha = 0;
self.upgradeButton.alpha = 0;
self.typeLabel.text = [MPAlgorithmDefault nameOfType:self.type];
if (self.type & MPElementTypeClassStored) {
self.contentField.enabled = YES;
self.contentField.placeholder = strl( @"Set custom password" );
}
else if (self.type & MPElementTypeClassGenerated) {
self.contentField.enabled = NO;
self.contentField.placeholder = strl( @"Generating..." );
}
else {
self.contentField.enabled = NO;
self.contentField.placeholder = nil;
}
self.contentField.text = nil;
[self resolveContentOfCellTypeForTransientSite:siteName usingKey:[MPiOSAppDelegate get].key result:^(NSString *string) {
PearlMainQueue( ^{ self.contentField.text = string; } );
}];
}
- (void)reloadWithElement:(MPElementEntity *)mainElement {
[super reloadWithElement:mainElement];
self.loginButton.alpha = 1;
self.typeLabel.text = [mainElement.algorithm nameOfType:self.type];
if (mainElement.requiresExplicitMigration)
self.upgradeButton.alpha = 1;
else
self.upgradeButton.alpha = 0;
switch (self.contentFieldMode) {
case MPContentFieldModePassword: {
if (self.type & MPElementTypeClassStored) {
self.contentField.enabled = YES;
self.contentField.placeholder = strl( @"Set custom password" );
}
else if (self.type & MPElementTypeClassGenerated) {
self.contentField.enabled = NO;
self.contentField.placeholder = strl( @"Generating..." );
}
else {
self.contentField.enabled = NO;
self.contentField.placeholder = nil;
}
self.contentField.text = nil;
MPKey *key = [MPiOSAppDelegate get].key;
if (self.type == mainElement.type)
[mainElement resolveContentUsingKey:key result:^(NSString *string) {
PearlMainQueue( ^{ self.contentField.text = string; } );
}];
else
[self resolveContentOfCellTypeForElement:mainElement usingKey:key result:^(NSString *string) {
PearlMainQueue( ^{ self.contentField.text = string; } );
}];
break;
}
case MPContentFieldModeUser: {
self.contentField.enabled = YES;
self.contentField.placeholder = strl( @"Enter login name" );
self.contentField.text = mainElement.loginName;
break;
}
}
}
- (void)resolveContentOfCellTypeForTransientSite:(NSString *)siteName usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock {
resultBlock( nil );
}
- (void)resolveContentOfCellTypeForElement:(MPElementEntity *)element usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock {
resultBlock( nil );
}
- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context {
return [[MPiOSAppDelegate get] changeElement:element saveInContext:context toType:self.type];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if (textField == self.contentField) {
NSString *newContent = textField.text;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context];
switch (self.contentFieldMode) {
case MPContentFieldModePassword:
break;
case MPContentFieldModeUser: {
element.loginName = newContent;
[context saveToStore];
PearlMainQueue( ^{
[self updateAnimated:YES];
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Updated" dismissAfter:2];
} );
break;
}
}
}];
}
}
#pragma mark - Actions
- (IBAction)doUser:(id)sender {
switch (self.contentFieldMode) {
case MPContentFieldModePassword: {
self.contentFieldMode = MPContentFieldModeUser;
break;
}
case MPContentFieldModeUser: {
self.contentFieldMode = MPContentFieldModePassword;
break;
}
}
}
- (IBAction)doUpgrade:(UIButton *)sender {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
if ([[[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context] migrateExplicitly:YES]) {
[context saveToStore];
PearlMainQueue( ^{
[[MPPasswordElementCell findAsSuperviewOf:self] reloadData];
[PearlOverlay showTemporaryOverlayWithTitle:@"Site Upgraded" dismissAfter:2];
} );
}
else
PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:@"Site Not Upgraded" dismissAfter:2];
} );
}];
}
#pragma mark - Properties
- (void)setContentFieldMode:(MPContentFieldMode)contentFieldMode {
if (_contentFieldMode == contentFieldMode)
return;
_contentFieldMode = contentFieldMode;
[[MPPasswordElementCell findAsSuperviewOf:self] reloadData];
}
@end

View File

@@ -0,0 +1,27 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordLargeGeneratedCell.h
// MPPasswordLargeGeneratedCell
//
// Created by lhunath on 2014-03-19.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPPasswordLargeCell.h"
@interface MPPasswordLargeGeneratedCell : MPPasswordLargeCell
@property(strong, nonatomic) IBOutlet UILabel *counterLabel;
@property(strong, nonatomic) IBOutlet UIButton *counterButton;
@end

View File

@@ -0,0 +1,139 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordLargeGeneratedCell.h
// MPPasswordLargeGeneratedCell
//
// Created by lhunath on 2014-03-19.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordLargeGeneratedCell.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPPasswordElementCell.h"
@implementation MPPasswordLargeGeneratedCell
- (void)awakeFromNib {
[super awakeFromNib];
UILongPressGestureRecognizer *gestureRecognizer = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(doResetCounterRecognizer:)];
[self.counterButton addGestureRecognizer:gestureRecognizer];
}
- (void)reloadWithElement:(MPElementEntity *)mainElement {
[super reloadWithElement:mainElement];
MPElementGeneratedEntity *generatedElement = [self generatedElement:mainElement];
if (generatedElement)
self.counterLabel.text = strf( @"%lu", (unsigned long)generatedElement.counter );
else
self.counterLabel.text = @"1";
if (!mainElement || mainElement.requiresExplicitMigration) {
self.counterLabel.alpha = 0;
self.counterButton.alpha = 0;
}
else {
self.counterLabel.alpha = 1;
self.counterButton.alpha = 1;
}
}
- (void)resolveContentOfCellTypeForTransientSite:(NSString *)siteName usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock {
PearlNotMainQueue( ^{
resultBlock( [MPAlgorithmDefault generateContentNamed:siteName ofType:self.type withCounter:1 usingKey:key] );
} );
}
- (void)resolveContentOfCellTypeForElement:(MPElementEntity *)element usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock {
id<MPAlgorithm> algorithm = element.algorithm;
NSString *siteName = element.name;
PearlNotMainQueue( ^{
resultBlock( [algorithm generateContentNamed:siteName ofType:self.type withCounter:1 usingKey:key] );
} );
}
- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context {
element = [super saveContentTypeWithElement:element saveInContext:context];
MPElementGeneratedEntity *generatedElement = [self generatedElement:element];
if (generatedElement) {
generatedElement.counter = [self.counterLabel.text intValue];
[context saveToStore];
}
return element;
}
#pragma mark - Actions
- (IBAction)doIncrementCounter:(UIButton *)sender {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementGeneratedEntity *generatedElement = [self generatedElementInContext:context];
if (!generatedElement)
return;
++generatedElement.counter;
[context saveToStore];
PearlMainQueue( ^{
[self updateAnimated:YES];
[PearlOverlay showTemporaryOverlayWithTitle:@"Counter Incremented" dismissAfter:2];
} );
}];
}
- (void)doResetCounterRecognizer:(UILongPressGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state != UIGestureRecognizerStateEnded)
return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementGeneratedEntity *generatedElement = [self generatedElementInContext:context];
if (!generatedElement)
return;
generatedElement.counter = 1;
[context saveToStore];
PearlMainQueue( ^{
[self updateAnimated:YES];
[PearlOverlay showTemporaryOverlayWithTitle:@"Counter Reset" dismissAfter:2];
} );
}];
}
#pragma mark - Properties
- (MPElementGeneratedEntity *)generatedElementInContext:(NSManagedObjectContext *)context {
return [self generatedElement:[[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context]];
}
- (MPElementGeneratedEntity *)generatedElement:(MPElementEntity *)element {
if (![element isKindOfClass:[MPElementGeneratedEntity class]])
return nil;
return (MPElementGeneratedEntity *)element;
}
@end

View File

@@ -0,0 +1,24 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordLargeStoredCell.h
// MPPasswordLargeStoredCell
//
// Created by lhunath on 2014-03-19.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPPasswordLargeCell.h"
@interface MPPasswordLargeStoredCell : MPPasswordLargeCell
@end

View File

@@ -0,0 +1,112 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordLargeGeneratedCell.h
// MPPasswordLargeGeneratedCell
//
// Created by lhunath on 2014-03-19.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordLargeStoredCell.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPPasswordElementCell.h"
@interface MPPasswordLargeStoredCell()
@property(strong, nonatomic) IBOutlet UIButton *editButton;
@end
@implementation MPPasswordLargeStoredCell
#pragma mark - Lifecycle
- (void)resolveContentOfCellTypeForElement:(MPElementEntity *)element usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock {
if (element.type & MPElementTypeClassStored)
[element resolveContentUsingKey:key result:resultBlock];
else
[super resolveContentOfCellTypeForElement:element usingKey:key result:resultBlock];
}
- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context {
element = [super saveContentTypeWithElement:element saveInContext:context];
MPElementStoredEntity *storedElement = [self storedElement:element];
if (storedElement) {
storedElement.contentObject = self.contentField.text;
[context saveToStore];
}
return element;
}
#pragma mark - Actions
- (IBAction)doEditContent:(UIButton *)sender {
UITextField *field = self.contentField;
field.enabled = YES;
[field becomeFirstResponder];
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidEndEditing:(UITextField *)textField {
[super textFieldDidEndEditing:textField];
if (textField == self.contentField) {
NSString *newContent = textField.text;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementStoredEntity *storedElement = [self storedElementInContext:context];
if (!storedElement)
return;
switch (self.contentFieldMode) {
case MPContentFieldModePassword: {
storedElement.contentObject = newContent;
[context saveToStore];
PearlMainQueue( ^{
[self updateAnimated:YES];
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
} );
break;
}
case MPContentFieldModeUser:
break;
}
}];
}
}
#pragma mark - Properties
- (MPElementStoredEntity *)storedElementInContext:(NSManagedObjectContext *)context {
return [self storedElement:[[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context]];
}
- (MPElementStoredEntity *)storedElement:(MPElementEntity *)element {
if (![element isKindOfClass:[MPElementStoredEntity class]])
return nil;
return (MPElementStoredEntity *)element;
}
@end

View File

@@ -0,0 +1,33 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordSmallCell.h
// MPPasswordSmallCell
//
// Created by lhunath on 2014-03-28.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPPasswordCell.h"
@interface MPPasswordSmallCell : MPPasswordElementCell
+ (instancetype)dequeueCellForElement:(MPElementEntity *)element
fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath;
@end
@interface MPPasswordSmallGeneratedCell : MPPasswordSmallCell
@end
@interface MPPasswordSmallStoredCell : MPPasswordSmallCell
@end

View File

@@ -0,0 +1,48 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordSmallCell.h
// MPPasswordSmallCell
//
// Created by lhunath on 2014-03-28.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordElementCell.h"
#import "MPPasswordSmallCell.h"
@implementation MPPasswordSmallCell {
}
+ (instancetype)dequeueCellForElement:(MPElementEntity *)element fromCollectionView:(UICollectionView *)collectionView
atIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier;
if (element.type & MPElementTypeClassGenerated)
reuseIdentifier = NSStringFromClass( [MPPasswordSmallGeneratedCell class] );
else if (element.type & MPElementTypeClassStored)
reuseIdentifier = NSStringFromClass( [MPPasswordSmallStoredCell class] );
else
Throw(@"Unexpected password type: %@", [MPAlgorithmDefault nameOfType:element.type]);
MPPasswordSmallCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
[cell setElement:element];
return cell;
}
@end
@implementation MPPasswordSmallGeneratedCell
@end
@implementation MPPasswordSmallStoredCell
@end

View File

@@ -0,0 +1,34 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordTypesCell.h
// MPPasswordTypesCell
//
// Created by lhunath on 2014-03-27.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPCell.h"
#import "MPPasswordCell.h"
#import "MPPasswordElementCell.h"
@interface MPPasswordTypesCell : MPPasswordElementCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property(nonatomic, strong) IBOutlet UICollectionView *contentCollectionView;
@property(nonatomic, strong) id<MPAlgorithm> algorithm;
+ (instancetype)dequeueCellForElement:(MPElementEntity *)element
fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)dequeueCellForTransientSite:(NSString *)siteName
fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath;
@end

View File

@@ -0,0 +1,218 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordTypesCell.h
// MPPasswordTypesCell
//
// Created by lhunath on 2014-03-27.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordTypesCell.h"
#import "MPPasswordLargeCell.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
@implementation MPPasswordTypesCell
#pragma mark - Lifecycle
+ (instancetype)dequeueCellForTransientSite:(NSString *)siteName fromCollectionView:(UICollectionView *)collectionView
atIndexPath:(NSIndexPath *)indexPath {
MPPasswordTypesCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass( [MPPasswordTypesCell class] )
forIndexPath:indexPath];
[cell setTransientSite:siteName];
return cell;
}
+ (instancetype)dequeueCellForElement:(MPElementEntity *)element fromCollectionView:(UICollectionView *)collectionView
atIndexPath:(NSIndexPath *)indexPath {
MPPasswordTypesCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass( [MPPasswordTypesCell class] )
forIndexPath:indexPath];
[cell setElement:element];
return cell;
}
- (void)awakeFromNib {
[super awakeFromNib];
self.backgroundColor = [UIColor clearColor];
self.layer.shadowColor = [UIColor clearColor].CGColor;
[self prepareForReuse];
}
- (void)prepareForReuse {
_algorithm = MPAlgorithmDefault;
[super prepareForReuse];
}
- (void)reloadWithTransientSite:(NSString *)siteName {
[super reloadWithTransientSite:siteName];
[self.contentCollectionView reloadData];
NSIndexPath *visibleIndexPath = [self contentIndexPathForType:
IfElse([[MPiOSAppDelegate get] activeUserForMainThread].defaultType, MPElementTypeGeneratedLong)];
[self.contentCollectionView scrollToItemAtIndexPath:visibleIndexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
- (void)reloadWithElement:(MPElementEntity *)mainElement {
[super reloadWithElement:mainElement];
self.algorithm = IfNotNilElse([self mainElement].algorithm, MPAlgorithmDefault);
[self.contentCollectionView reloadData];
NSIndexPath *visibleIndexPath = [self contentIndexPathForType:mainElement.type];
[self.contentCollectionView scrollToItemAtIndexPath:visibleIndexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
if (!self.algorithm)
dbg_return_tr(0, @, @(section));
NSInteger types = 1;
MPElementType type = [self typeForContentIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
for (MPElementType nextType = type; type != (nextType = [self.algorithm nextType:nextType]);)
++types;
dbg_return_tr(types, @, @(section));
}
- (MPPasswordLargeCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MPPasswordLargeCell *cell = [MPPasswordLargeCell dequeueCellWithType:[self typeForContentIndexPath:indexPath]
fromCollectionView:collectionView atIndexPath:indexPath];
if (self.transientSite)
[cell reloadWithTransientSite:self.transientSite];
else
[cell reloadWithElement:self.mainElement];
dbg_return(cell, indexPath);
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionView *passwordCollectionView = [UICollectionView findAsSuperviewOf:self];
[passwordCollectionView.delegate collectionView:passwordCollectionView didSelectItemAtIndexPath:[passwordCollectionView indexPathForCell:self]];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset {
if (scrollView == self.contentCollectionView) {
NSIndexPath *targetIndexPath = [self.contentCollectionView indexPathForItemAtPoint:
CGPointPlusCGPoint( *targetContentOffset, self.contentCollectionView.center )];
*targetContentOffset = CGPointFromCGRectTopLeft(
[self.contentCollectionView layoutAttributesForItemAtIndexPath:targetIndexPath].frame );
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (scrollView == self.contentCollectionView && !decelerate)
[self saveContentType];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView == self.contentCollectionView)
[self saveContentType];
}
#pragma mark - Private
- (MPElementType)typeForContentIndexPath:(NSIndexPath *)indexPath {
MPElementType type = MPElementTypeGeneratedPIN;
for (NSUInteger i = 0; i < indexPath.item; ++i)
type = [self.algorithm nextType:type];
return type;
}
- (NSIndexPath *)contentIndexPathForType:(MPElementType)type {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
while ([self typeForContentIndexPath:indexPath] != type) {
indexPath = [NSIndexPath indexPathForItem:indexPath.item + 1 inSection:indexPath.section];
NSAssert1(indexPath.item < [self.contentCollectionView numberOfItemsInSection:0],
@"No item found for type: %@", [self.algorithm nameOfType:type]);
}
return indexPath;
}
- (void)saveContentType {
if (self.transientSite)
return;
CGPoint centerPoint = CGPointFromCGRectCenter( self.contentCollectionView.bounds );
NSIndexPath *centerIndexPath = [self.contentCollectionView indexPathForItemAtPoint:centerPoint];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPPasswordLargeCell *cell = (MPPasswordLargeCell *)[self.contentCollectionView cellForItemAtIndexPath:centerIndexPath];
if (!cell) {
err(@"Couldn't find cell to change type: centerIndexPath=%@", centerIndexPath);
return;
}
MPElementEntity *element = [self elementInContext:context];
if (element.type == cell.type)
// Nothing changed.
return;
self.element = [cell saveContentTypeWithElement:element saveInContext:context];
}];
}
#pragma mark - Properties
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (!selected)
for (NSIndexPath *indexPath in [self.contentCollectionView indexPathsForSelectedItems])
[self.contentCollectionView deselectItemAtIndexPath:indexPath animated:YES];
}
- (void)setAlgorithm:(id<MPAlgorithm>)algorithm {
if ([_algorithm isEqual:algorithm])
return;
_algorithm = algorithm;
[self.contentCollectionView reloadData];
}
@end

View File

@@ -0,0 +1,23 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordsCoachmarkViewController.h
// MPPasswordsCoachmarkViewController
//
// Created by lhunath on 2014-04-23.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPCoachmarkViewController.h"
@interface MPPasswordsCoachmarkViewController : MPCoachmarkViewController <UICollectionViewDataSource>
@end

View File

@@ -0,0 +1,51 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordsCoachmarkViewController.h
// MPPasswordsCoachmarkViewController
//
// Created by lhunath on 2014-04-23.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordsCoachmarkViewController.h"
#import "MPPasswordLargeGeneratedCell.h"
#import "MPPasswordLargeStoredCell.h"
@implementation MPPasswordsCoachmarkViewController
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 2;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.item == 0) {
MPPasswordLargeGeneratedCell *cell = [MPPasswordLargeGeneratedCell dequeueCellWithType:MPElementTypeGeneratedLong
fromCollectionView:collectionView atIndexPath:indexPath];
[cell reloadWithTransientSite:@"apple.com"];
return cell;
}
else if (indexPath.item == 1) {
MPPasswordLargeStoredCell *cell = [MPPasswordLargeStoredCell dequeueCellWithType:MPElementTypeStoredPersonal
fromCollectionView:collectionView atIndexPath:indexPath];
[cell reloadWithTransientSite:@"gmail.com"];
[cell.contentField setText:@"PaS$w0rD"];
return cell;
}
Throw(@"Unexpected item for indexPath: %@", indexPath);
}
@end

View File

@@ -0,0 +1,25 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordsSegue.h
// MPPasswordsSegue
//
// Created by lhunath on 2014-04-12.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MPPasswordsSegue : UIStoryboardSegue
@property (nonatomic, assign) BOOL animated;
@end

View File

@@ -0,0 +1,65 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordsSegue.h
// MPPasswordsSegue
//
// Created by lhunath on 2014-04-12.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordsSegue.h"
#import "MPPasswordsViewController.h"
#import "MPCombinedViewController.h"
@implementation MPPasswordsSegue {
}
- (id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
if (!(self = [super initWithIdentifier:identifier source:source destination:destination]))
return nil;
self.animated = YES;
return self;
}
- (void)perform {
if ([self.destinationViewController isKindOfClass:[MPPasswordsViewController class]]) {
__weak MPPasswordsViewController *passwordsVC = self.destinationViewController;
__weak MPCombinedViewController *combinedVC = self.sourceViewController;
[combinedVC addChildViewController:passwordsVC];
[combinedVC.view insertSubview:passwordsVC.view belowSubview:combinedVC.usersView];
passwordsVC.active = NO;
[passwordsVC setActive:YES animated:self.animated completion:^(BOOL finished) {
if (!finished)
return;
[passwordsVC didMoveToParentViewController:combinedVC];
}];
} else if ([self.sourceViewController isKindOfClass:[MPPasswordsViewController class]]) {
__weak MPPasswordsViewController *passwordsVC = self.sourceViewController;
[passwordsVC willMoveToParentViewController:nil];
[passwordsVC setActive:NO animated:self.animated completion:^(BOOL finished) {
if (!finished)
return;
[passwordsVC.view removeFromSuperview];
[passwordsVC removeFromParentViewController];
}];
}
}
@end

View File

@@ -0,0 +1,44 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCombinedViewController.h
// MPCombinedViewController
//
// Created by lhunath on 2014-03-08.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "LLGitTip.h"
@class MPElementEntity;
@class MPCoachmark;
@interface MPPasswordsViewController : UIViewController<UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property(strong, nonatomic) IBOutlet UIView *passwordSelectionContainer;
@property(strong, nonatomic) IBOutlet UICollectionView *passwordCollectionView;
@property(strong, nonatomic) IBOutlet UISearchBar *passwordsSearchBar;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsToBottomConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *popdownToTopConstraint;
@property(strong, nonatomic) IBOutlet UIView *popdownView;
@property(strong, nonatomic) IBOutlet UIView *popdownContainer;
@property(assign, nonatomic) BOOL active;
@property(nonatomic, copy) NSString *originalQuery;
@property(nonatomic, readonly) MPCoachmark *coachmark;
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void (^)(BOOL finished))completion;
- (IBAction)dismissPopdown:(id)sender;
- (IBAction)signOut:(id)sender;
@end

View File

@@ -0,0 +1,622 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordsViewController.h
// MPPasswordsViewController
//
// Created by lhunath on 2014-03-08.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordsViewController.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPPasswordLargeCell.h"
#import "MPPasswordTypesCell.h"
#import "MPPasswordSmallCell.h"
#import "MPPopdownSegue.h"
#import "MPAppDelegate_Key.h"
#import "MPCoachmarkViewController.h"
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
@property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar;
@property(nonatomic, readonly) NSString *query;
@end
@implementation MPPasswordsViewController {
__weak id _storeObserver;
__weak id _mocObserver;
NSArray *_notificationObservers;
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
NSFetchedResultsController *_fetchedResultsController;
BOOL _exactMatch;
NSMutableDictionary *_fetchedUpdates;
UIColor *_backgroundColor;
UIColor *_darkenedBackgroundColor;
__weak UIViewController *_popdownVC;
}
#pragma mark - Life
- (void)viewDidLoad {
[super viewDidLoad];
_fetchedUpdates = [NSMutableDictionary dictionaryWithCapacity:4];
_backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_coachmark = [MPCoachmark coachmarkForClass:[self class] version:0];
self.view.backgroundColor = [UIColor clearColor];
self.passwordCollectionView.contentInset = UIEdgeInsetsMake( 108, 0, 0, 0 );
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self registerObservers];
[self observeStore];
[self updatePasswords];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
PearlMainQueueAfter( 1, ^{
if (!self.coachmark.coached)
[self performSegueWithIdentifier:@"coachmarks" sender:self];
} );
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self removeObservers];
[self stopObservingStore];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"popdown"])
_popdownVC = segue.destinationViewController;
if ([segue.identifier isEqualToString:@"coachmarks"])
((MPCoachmarkViewController *)segue.destinationViewController).coachmark = self.coachmark;
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
if (collectionView == self.passwordCollectionView) {
if (indexPath.item < 3 ||
indexPath.item >= ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
return CGSizeMake( 300, 100 );
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
return CGSizeMake( (300 - layout.minimumInteritemSpacing) / 2, 44 );
}
Throw(@"Unexpected collection view: %@", collectionView);
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
if (collectionView == self.passwordCollectionView)
return [self.fetchedResultsController.sections count];
Throw(@"Unexpected collection view: %@", collectionView);
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
if (collectionView == self.passwordCollectionView)
return ![MPiOSAppDelegate get].activeUserOID? 0:
((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[section]).numberOfObjects +
(!_exactMatch && [[self query] length]? 1: 0);
Throw(@"Unexpected collection view: %@", collectionView);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (collectionView == self.passwordCollectionView) {
[UIView setAnimationsEnabled:NO];
MPPasswordElementCell *cell;
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) {
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (indexPath.item < 3)
cell = [MPPasswordTypesCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
else
cell = [MPPasswordSmallCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
}
else
// New Site.
cell = [MPPasswordTypesCell dequeueCellForTransientSite:self.query fromCollectionView:collectionView atIndexPath:indexPath];
[UIView setAnimationsEnabled:YES];
return cell;
}
Throw(@"Unexpected collection view: %@", collectionView);
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
MPPasswordElementCell *cell = (MPPasswordElementCell *)[collectionView cellForItemAtIndexPath:indexPath];
NSString *newSiteName = cell.transientSite;
if (newSiteName) {
[PearlAlert showAlertWithTitle:@"Create Site"
message:strf( @"Do you want to create a new site named:\n%@", newSiteName )
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) {
// Cancel
NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
[collectionView selectItemAtIndexPath:indexPath_ animated:NO
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
[collectionView deselectItemAtIndexPath:indexPath_ animated:YES];
return;
}
// Create
[[MPiOSAppDelegate get] addElementNamed:newSiteName completion:^(MPElementEntity *element) {
PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Added %@", newSiteName ) dismissAfter:2];
PearlMainQueueAfter( 0.2f, ^{
NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
[collectionView selectItemAtIndexPath:indexPath_ animated:NO
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
[collectionView deselectItemAtIndexPath:indexPath_ animated:YES];
} );
} );
}];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
return;
}
MPElementEntity *element = [cell mainElement];
if (!element) {
[collectionView selectItemAtIndexPath:indexPath animated:NO
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
[collectionView deselectItemAtIndexPath:indexPath animated:YES];
return;
}
inf(@"Copying password for: %@", element.name);
MPCheckpoint( MPCheckpointCopyToPasteboard, @{
@"type" : NilToNSNull(element.typeName),
@"version" : @(element.version),
@"emergency" : @NO
} );
[element resolveContentUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
if (![result length]) {
PearlMainQueue( ^{
NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
[collectionView selectItemAtIndexPath:indexPath_ animated:NO
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
[collectionView deselectItemAtIndexPath:indexPath_ animated:YES];
} );
return;
}
[UIPasteboard generalPasteboard].string = result;
PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2];
PearlMainQueueAfter( 0.2f, ^{
NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
[collectionView selectItemAtIndexPath:indexPath_ animated:NO
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
[collectionView deselectItemAtIndexPath:indexPath_ animated:YES];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[[cell elementInContext:context] use];
[context saveToStore];
}];
} );
} );
}];
}
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
if (controller == _fetchedResultsController) {
dbg(@"controllerWillChangeContent");
NSAssert(![_fetchedUpdates count], @"Didn't finish a previous change update?");
if ([_fetchedUpdates count]) {
[_fetchedUpdates removeAllObjects];
[self.passwordCollectionView reloadData];
}
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (controller == _fetchedResultsController) {
NSMutableArray *updatesForType = _fetchedUpdates[@(type)];
if (!updatesForType)
_fetchedUpdates[@(type)] = updatesForType = [NSMutableArray new];
[updatesForType addObject:@{
@"object" : NilToNSNull(anObject),
@"indexPath" : NilToNSNull(indexPath),
@"newIndexPath" : NilToNSNull(newIndexPath)
}];
switch (type) {
case NSFetchedResultsChangeInsert:
dbg(@"didChangeObject: insert: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeDelete:
dbg(@"didChangeObject: delete: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeMove:
dbg(@"didChangeObject: move: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeUpdate:
dbg(@"didChangeObject: update: %@", [updatesForType lastObject]);
break;
}
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
if (controller == _fetchedResultsController) {
NSMutableArray *updatesForType = _fetchedUpdates[@(type << 3)];
if (!updatesForType)
_fetchedUpdates[@(type << 3)] = updatesForType = [NSMutableArray new];
[updatesForType addObject:@{
@"sectionInfo" : NilToNSNull(sectionInfo),
@"index" : @(sectionIndex)
}];
switch (type) {
case NSFetchedResultsChangeInsert:
dbg(@"didChangeSection: insert: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeDelete:
dbg(@"didChangeSection: delete: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeMove:
dbg(@"didChangeSection: move: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeUpdate:
dbg(@"didChangeSection: update: %@", [updatesForType lastObject]);
break;
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (controller == _fetchedResultsController && [_fetchedUpdates count]) {
[self.passwordCollectionView performBatchUpdates:^{
[_fetchedUpdates enumerateKeysAndObjectsUsingBlock:^(NSNumber *typeNumber, NSArray *updates, BOOL *stop) {
BOOL updateIsSection = NO;
NSFetchedResultsChangeType type = [typeNumber unsignedIntegerValue];
if (type >= 1 << 3) {
updateIsSection = YES;
type = type >> 3;
}
switch (type) {
case NSFetchedResultsChangeInsert:
if (updateIsSection) {
for (NSDictionary *update in updates) {
dbg(@"insertSections:%@", update[@"index"]);
[self.passwordCollectionView insertSections:
[NSIndexSet indexSetWithIndex:[update[@"index"] unsignedIntegerValue]]];
}
}
else {
dbg(@"insertItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.newIndexPath"]);
[self.passwordCollectionView insertItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.newIndexPath"]];
}
break;
case NSFetchedResultsChangeDelete:
if (updateIsSection) {
for (NSDictionary *update in updates) {
dbg(@"deleteSections:%@", update[@"index"]);
[self.passwordCollectionView deleteSections:
[NSIndexSet indexSetWithIndex:[update[@"index"] unsignedIntegerValue]]];
}
}
else {
dbg(@"deleteItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.indexPath"]);
[self.passwordCollectionView deleteItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.indexPath"]];
}
break;
case NSFetchedResultsChangeMove:
NSAssert(!updateIsSection, @"Move not supported for sections");
for (NSDictionary *update in updates) {
dbg(@"moveItemAtIndexPath:%@ toIndexPath:%@", update[@"indexPath"], update[@"newIndexPath"]);
[self.passwordCollectionView moveItemAtIndexPath:update[@"indexPath"] toIndexPath:update[@"newIndexPath"]];
}
break;
case NSFetchedResultsChangeUpdate:
NSAssert(!updateIsSection, @"Update not supported for sections");
dbg(@"reloadItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.indexPath"]);
[self.passwordCollectionView reloadItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.indexPath"]];
break;
}
}];
} completion:nil];
[_fetchedUpdates removeAllObjects];
}
}
#pragma mark - UIScrollViewDelegate
#pragma mark - UISearchBarDelegate
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
if (searchBar == self.passwordsSearchBar) {
searchBar.text = nil;
return YES;
}
return NO;
}
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
if (searchBar == self.passwordsSearchBar) {
self.originalQuery = self.query;
self.passwordsSearchBar.showsCancelButton = YES;
_passwordsDismissRecognizer = [self.view dismissKeyboardForField:self.passwordsSearchBar onTouchForced:NO];
[UIView animateWithDuration:0.3f animations:^{
self.passwordCollectionView.backgroundColor = _darkenedBackgroundColor;
}];
}
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (searchBar == self.passwordsSearchBar) {
self.passwordsSearchBar.showsCancelButton = NO;
if (_passwordsDismissRecognizer)
[self.view removeGestureRecognizer:_passwordsDismissRecognizer];
[UIView animateWithDuration:0.3f animations:^{
self.passwordCollectionView.backgroundColor = _backgroundColor;
}];
}
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];
if (searchBar == self.passwordsSearchBar) {
self.passwordsSearchBar.text = self.originalQuery;
[self updatePasswords];
}
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchBar == self.passwordsSearchBar)
[self updatePasswords];
}
#pragma mark - Private
- (void)registerObservers {
if ([_notificationObservers count])
return;
Weakify(self);
_notificationObservers = @[
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
self.passwordSelectionContainer.alpha = 0;
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify(self);
_fetchedResultsController = nil;
self.passwordsSearchBar.text = nil;
[self updatePasswords];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
[self updatePasswords];
[UIView animateWithDuration:1 animations:^{
self.passwordSelectionContainer.alpha = 1;
}];
}],
];
}
- (void)removeObservers {
for (id observer in _notificationObservers)
[[NSNotificationCenter defaultCenter] removeObserver:observer];
_notificationObservers = nil;
}
- (void)observeStore {
Weakify(self);
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
if (!_mocObserver && mainContext)
_mocObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// Strongify(self);
// [self updatePasswords];
}];
if (!_storeObserver)
_storeObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:USMStoreDidChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
_fetchedResultsController = nil;
[self updatePasswords];
}];
}
- (void)stopObservingStore {
if (_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
if (_storeObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
}
- (void)updatePasswords {
NSString *query = self.query;
NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID;
if (!activeUserOID) {
self.passwordsSearchBar.text = nil;
PearlMainQueue( ^{
[self.passwordCollectionView reloadData];
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) animated:YES];
} );
return;
}
[self.fetchedResultsController.managedObjectContext performBlock:^{
NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate =
[query length]?
[NSPredicate predicateWithFormat:@"user == %@ AND name BEGINSWITH[cd] %@", activeUserOID, query]:
[NSPredicate predicateWithFormat:@"user == %@", activeUserOID];
if (![self.fetchedResultsController performFetch:&error])
err(@"Couldn't fetch elements: %@", error);
_exactMatch = NO;
for (MPElementEntity *entity in self.fetchedResultsController.fetchedObjects)
if ([entity.name isEqualToString:query]) {
_exactMatch = YES;
break;
}
PearlMainQueue( ^{
[self.passwordCollectionView performBatchUpdates:^{
NSInteger fromSections = self.passwordCollectionView.numberOfSections;
NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView];
for (int section = 0; section < MAX(toSections, fromSections); section++) {
if (section >= fromSections) {
dbg(@"insertSections:%d", section);
[self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
}
else if (section >= toSections) {
dbg(@"deleteSections:%d", section);
[self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
}
else {
dbg(@"reloadSections:%d", section);
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:section]];
}
}
} completion:^(BOOL finished) {
if (finished)
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top )
animated:YES];
}];
} );
}];
}
#pragma mark - Properties
- (NSString *)query {
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (!_fetchedResultsController) {
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
fetchRequest.sortDescriptors = @[
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector(lastUsed) ) ascending:NO]
];
fetchRequest.fetchBatchSize = 10;
_fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
}];
[self observeStore];
}
return _fetchedResultsController;
}
- (void)setActive:(BOOL)active {
[self setActive:active animated:NO completion:nil];
}
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void (^)(BOOL finished))completion {
_active = active;
[UIView animateWithDuration:animated? 0.4f: 0 animations:^{
self.navigationBarToTopConstraint.priority = active? 1: UILayoutPriorityDefaultHigh;
self.passwordsToBottomConstraint.priority = active? 1: UILayoutPriorityDefaultHigh;
[self.navigationBarToTopConstraint apply];
[self.passwordsToBottomConstraint apply];
} completion:completion];
}
#pragma mark - Actions
- (IBAction)dismissPopdown:(id)sender {
if (_popdownVC)
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:_popdownVC destination:self] perform];
else
self.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh;
}
- (IBAction)signOut:(id)sender {
[[MPiOSAppDelegate get] signOutAnimated:YES];
}
@end

View File

@@ -0,0 +1,22 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPopdownSegue.h
// MPPopdownSegue
//
// Created by lhunath on 2014-04-17.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MPPopdownSegue : UIStoryboardSegue
@end

View File

@@ -0,0 +1,65 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPopdownSegue.h
// MPPopdownSegue
//
// Created by lhunath on 2014-04-17.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPopdownSegue.h"
#import "MPPasswordsViewController.h"
@implementation MPPopdownSegue {
}
- (void)perform {
MPPasswordsViewController *passwordsVC;
UIViewController *popdownVC;
if ([self.sourceViewController isKindOfClass:[MPPasswordsViewController class]]) {
passwordsVC = self.sourceViewController;
popdownVC = self.destinationViewController;
UIView *popdownView = popdownVC.view;
popdownView.translatesAutoresizingMaskIntoConstraints = NO;
[passwordsVC addChildViewController:popdownVC];
[passwordsVC.popdownContainer addSubview:popdownView];
[passwordsVC.popdownContainer addConstraintsWithVisualFormats:@[ @"H:|[popdownView]|", @"V:|[popdownView]|" ] options:0
metrics:nil views:NSDictionaryOfVariableBindings(popdownView)];
[UIView animateWithDuration:0.3f animations:^{
passwordsVC.popdownToTopConstraint.priority = 1;
[passwordsVC.popdownToTopConstraint apply];
} completion:^(BOOL finished) {
if (finished)
[popdownVC didMoveToParentViewController:passwordsVC];
}];
}
else if ([self.destinationViewController isKindOfClass:[MPPasswordsViewController class]]) {
popdownVC = self.sourceViewController;
passwordsVC = self.destinationViewController;
[popdownVC willMoveToParentViewController:nil];
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{
passwordsVC.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh;
[passwordsVC.popdownToTopConstraint apply];
} completion:^(BOOL finished) {
if (finished) {
[popdownVC.view removeFromSuperview];
[popdownVC removeFromParentViewController];
}
}];
}
}
@end

View File

@@ -9,15 +9,17 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "MPTypeViewController.h" #import "MPTypeViewController.h"
@interface MPPreferencesViewController : UITableViewController<MPTypeDelegate> @interface MPPreferencesViewController : UITableViewController
@property(weak, nonatomic) IBOutlet UIScrollView *avatarsView;
@property(weak, nonatomic) IBOutlet UIButton *avatarTemplate;
@property(weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch; @property(weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch;
@property(weak, nonatomic) IBOutlet UITableViewCell *feedbackCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *coachmarksCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *exportCell; @property(weak, nonatomic) IBOutlet UITableViewCell *exportCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *changeMPCell; @property(weak, nonatomic) IBOutlet UIImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UILabel *defaultTypeLabel; @property(weak, nonatomic) IBOutlet UISegmentedControl *typeControl;
- (IBAction)didToggleSwitch:(UISwitch *)sender; - (IBAction)previousAvatar:(id)sender;
- (IBAction)nextAvatar:(id)sender;
- (IBAction)valueChanged:(id)sender;
@end @end

View File

@@ -6,12 +6,13 @@
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import <QuartzCore/QuartzCore.h>
#import "MPPreferencesViewController.h" #import "MPPreferencesViewController.h"
#import "MPiOSAppDelegate.h" #import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "UIColor+Expanded.h"
#import "MPPasswordsViewController.h"
#import "MPCoachmarkViewController.h"
@interface MPPreferencesViewController() @interface MPPreferencesViewController()
@@ -21,158 +22,146 @@
- (void)viewDidLoad { - (void)viewDidLoad {
self.avatarTemplate.hidden = YES;
for (int a = 0; a < MPAvatarCount; ++a) {
UIButton *avatar = [self.avatarTemplate clone];
avatar.tag = a;
avatar.hidden = NO;
avatar.center = CGPointMake(
self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
self.avatarTemplate.center.y );
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%d", a )]
forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 5;
avatar.backgroundColor = [UIColor clearColor];
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected)
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
else
avatar.backgroundColor = [UIColor clearColor];
} options:0];
[avatar onSelect:^(BOOL selected) {
if (selected) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
[[MPiOSAppDelegate get] activeUserInContext:moc].avatar = (unsigned)avatar.tag;
[moc saveToStore];
}];
}
} options:0];
avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForMainThread].avatar);
}
[super viewDidLoad]; [super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
inf(@"Preferences will appear"); inf(@"Preferences will appear");
[self.avatarsView autoSizeContent]; [super viewWillAppear:animated];
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if (subview.tag && ((UIControl *)subview).selected) {
[self.avatarsView setContentOffset:CGPointMake( subview.center.x - self.avatarsView.bounds.size.width / 2, 0 )
animated:animated];
}
} recurse:NO];
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread]; MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
self.typeControl.selectedSegmentIndex = [self segmentIndexForType:activeUser.defaultType];
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%ld", (long)activeUser.avatar )];
self.savePasswordSwitch.on = activeUser.saveKey; self.savePasswordSwitch.on = activeUser.saveKey;
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType];
[super viewWillAppear:animated]; self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
}
- (void)viewDidAppear:(BOOL)animated {
#ifdef LOCALYTICS
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Preferences"];
#endif
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Preferences will disappear");
[super viewWillDisappear:animated];
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (motion == UIEventSubtypeMotionShake) {
MPCheckpoint( MPCheckpointLogs, @{
@"trace" : [MPiOSConfig get].traceMode
} );
[self performSegueWithIdentifier:@"MP_Logs" sender:self];
}
}
- (BOOL)shouldAutorotate {
return NO;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:@"MP_ChooseType"])
((MPTypeViewController *)[segue destinationViewController]).delegate = self;
} }
#pragma mark - UITableViewDelegate #pragma mark - UITableViewDelegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell.selectionStyle != UITableViewCellSelectionStyleNone) {
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRGBAHex:0x78DDFB33];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath]; UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell == self.feedbackCell)
[[MPiOSAppDelegate get] showFeedbackWithLogs:YES forVC:self];
if (cell == self.exportCell) if (cell == self.exportCell)
[[MPiOSAppDelegate get] export]; [[MPiOSAppDelegate get] showExportForVC:self];
if (cell == self.coachmarksCell) {
else if (cell == self.changeMPCell) { for (UIViewController *vc = self; (vc = vc.parentViewController); )
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { if ([vc isKindOfClass:[MPPasswordsViewController class]]) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; MPPasswordsViewController *passwordsVC = (MPPasswordsViewController *)vc;
[[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil]; passwordsVC.coachmark.coached = NO;
}]; [passwordsVC dismissPopdown:self];
[vc performSegueWithIdentifier:@"coachmarks" sender:self];
}
} }
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
} }
#pragma mark - MPTypeDelegate
- (void)didSelectType:(MPElementType)type {
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:type];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
activeUser.defaultType = type;
[context saveToStore];
}];
}
- (MPElementType)selectedType {
return [[MPiOSAppDelegate get] activeUserForMainThread].defaultType;
}
#pragma mark - IBActions #pragma mark - IBActions
- (IBAction)didToggleSwitch:(UISwitch *)sender { - (IBAction)valueChanged:(id)sender {
if (sender == self.savePasswordSwitch) if (sender == self.savePasswordSwitch)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc]; MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
if ((activeUser.saveKey = sender.on)) if ((activeUser.saveKey = self.savePasswordSwitch.on))
[[MPiOSAppDelegate get] storeSavedKeyFor:activeUser]; [[MPiOSAppDelegate get] storeSavedKeyFor:activeUser];
else else
[[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser]; [[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser];
[moc saveToStore]; [context saveToStore];
}];
if (sender == self.typeControl)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType = [self typeForSelectedSegment];
[context saveToStore];
}]; }];
} }
- (IBAction)previousAvatar:(id)sender {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
activeUser.avatar = (activeUser.avatar - 1 + MPAvatarCount) % MPAvatarCount;
[context saveToStore];
long avatar = activeUser.avatar;
PearlMainQueue( ^{
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%ld", avatar )];
} );
}];
}
- (IBAction)nextAvatar:(id)sender {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
activeUser.avatar = (activeUser.avatar + 1 + MPAvatarCount) % MPAvatarCount;
[context saveToStore];
long avatar = activeUser.avatar;
PearlMainQueue( ^{
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%ld", avatar )];
} );
}];
}
#pragma mark - Private
- (enum MPElementType)typeForSelectedSegment {
switch (self.typeControl.selectedSegmentIndex) {
case 0:
return MPElementTypeGeneratedMaximum;
case 1:
return MPElementTypeGeneratedLong;
case 2:
return MPElementTypeGeneratedMedium;
case 3:
return MPElementTypeGeneratedBasic;
case 4:
return MPElementTypeGeneratedShort;
case 5:
return MPElementTypeGeneratedPIN;
default:
Throw(@"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex);
}
}
- (NSInteger)segmentIndexForType:(MPElementType)type {
switch (type) {
case MPElementTypeGeneratedMaximum:
return 0;
case MPElementTypeGeneratedLong:
return 1;
case MPElementTypeGeneratedMedium:
return 2;
case MPElementTypeGeneratedBasic:
return 3;
case MPElementTypeGeneratedShort:
return 4;
case MPElementTypeGeneratedPIN:
return 5;
default:
Throw(@"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex);
}
}
@end @end

View File

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

View File

@@ -0,0 +1,178 @@
//
// MPPreferencesViewController.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import "MPPreferencesViewControllerOld.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
@interface MPPreferencesViewControllerOld()
@end
@implementation MPPreferencesViewControllerOld
- (void)viewDidLoad {
self.avatarTemplate.hidden = YES;
for (NSUInteger a = 0; a < MPAvatarCount; ++a) {
UIButton *avatar = [self.avatarTemplate clone];
avatar.tag = a;
avatar.hidden = NO;
avatar.center = CGPointMake(
self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
self.avatarTemplate.center.y );
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%ld", (long)a )]
forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 5;
avatar.backgroundColor = [UIColor clearColor];
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected)
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
else
avatar.backgroundColor = [UIColor clearColor];
} options:0];
[avatar onSelect:^(BOOL selected) {
if (selected) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
[[MPiOSAppDelegate get] activeUserInContext:moc].avatar = (unsigned)avatar.tag;
[moc saveToStore];
}];
}
} options:0];
avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForMainThread].avatar);
}
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
inf(@"Preferences will appear");
[self.avatarsView autoSizeContent];
[self.avatarsView enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if (subview.tag && ((UIControl *)subview).selected) {
[self.avatarsView setContentOffset:CGPointMake( subview.center.x - self.avatarsView.bounds.size.width / 2, 0 )
animated:animated];
}
} recurse:NO];
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
self.savePasswordSwitch.on = activeUser.saveKey;
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType];
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
#ifdef LOCALYTICS
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Preferences"];
#endif
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
inf(@"Preferences will disappear");
[super viewWillDisappear:animated];
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (motion == UIEventSubtypeMotionShake) {
MPCheckpoint( MPCheckpointLogs, @{
@"trace" : [MPiOSConfig get].traceMode
} );
[self performSegueWithIdentifier:@"MP_Logs" sender:self];
}
}
- (BOOL)shouldAutorotate {
return NO;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:@"MP_ChooseType"])
((MPTypeViewController *)[segue destinationViewController]).delegate = self;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell == self.exportCell)
[[MPiOSAppDelegate get] showExportForVC:self];
else if (cell == self.changeMPCell) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
[[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil];
}];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - MPTypeDelegate
- (void)didSelectType:(MPElementType)type {
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:type];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
activeUser.defaultType = type;
[context saveToStore];
}];
}
- (MPElementType)selectedType {
return [[MPiOSAppDelegate get] activeUserForMainThread].defaultType;
}
#pragma mark - IBActions
- (IBAction)didToggleSwitch:(UISwitch *)sender {
if (sender == self.savePasswordSwitch)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
if ((activeUser.saveKey = sender.on))
[[MPiOSAppDelegate get] storeSavedKeyFor:activeUser];
else
[[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser];
[moc saveToStore];
}];
}
@end

View File

@@ -54,12 +54,4 @@
[self dismissViewControllerAnimated:YES completion:nil]; [self dismissViewControllerAnimated:YES completion:nil];
} }
- (IBAction)showGuide:(UIBarButtonItem *)sender {
[MPiOSConfig get].showSetup = @NO;
[self dismissViewControllerAnimated:YES completion:^{
[[MPiOSAppDelegate get] showGuide];
}];
}
@end @end

View File

@@ -7,6 +7,7 @@
// //
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "LLGitTip.h"
@interface MPUnlockViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate, UIWebViewDelegate> @interface MPUnlockViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate, UIWebViewDelegate>
@@ -26,6 +27,8 @@
@property(strong, nonatomic) IBOutlet UILongPressGestureRecognizer *targetedUserActionGesture; @property(strong, nonatomic) IBOutlet UILongPressGestureRecognizer *targetedUserActionGesture;
@property(weak, nonatomic) IBOutlet UIView *uiContainer; @property(weak, nonatomic) IBOutlet UIView *uiContainer;
@property(weak, nonatomic) IBOutlet UIView *shareContainer; @property(weak, nonatomic) IBOutlet UIView *shareContainer;
@property(weak, nonatomic) IBOutlet UIView *tipsTipContainer;
@property(weak, nonatomic) IBOutlet LLGitTip *gitTipButton;
@property(weak, nonatomic) IBOutlet UIWebView *newsView; @property(weak, nonatomic) IBOutlet UIWebView *newsView;
@property(weak, nonatomic) IBOutlet UIView *emergencyGeneratorContainer; @property(weak, nonatomic) IBOutlet UIView *emergencyGeneratorContainer;
@property(weak, nonatomic) IBOutlet UITextField *emergencyName; @property(weak, nonatomic) IBOutlet UITextField *emergencyName;

View File

@@ -7,12 +7,12 @@
// //
#import <Social/Social.h> #import <Social/Social.h>
#import <QuartzCore/QuartzCore.h>
#import "MPUnlockViewController.h" #import "MPUnlockViewController.h"
#import "MPiOSAppDelegate.h" #import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "LLGitTip.h"
@interface MPUnlockViewController() @interface MPUnlockViewController()
@@ -26,12 +26,18 @@
@property(nonatomic, strong) NSTimer *marqueeTipTimer; @property(nonatomic, strong) NSTimer *marqueeTipTimer;
@property(nonatomic) NSUInteger marqueeTipTextIndex; @property(nonatomic) NSUInteger marqueeTipTextIndex;
@property(nonatomic, strong) NSArray *marqueeTipTexts; @property(nonatomic, strong) NSArray *marqueeTipTexts;
@property(nonatomic, strong) id mocObserver;
@end @end
@implementation MPUnlockViewController { @implementation MPUnlockViewController {
NSManagedObjectID *_selectedUserOID; NSManagedObjectID *_selectedUserOID;
} }
- (void)dealloc {
if (self.mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self.mocObserver];
}
- (void)initializeAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc { - (void)initializeAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
UIScrollView *alertAvatarScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake( 12, 30, 260, 150 )]; UIScrollView *alertAvatarScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake( 12, 30, 260, 150 )];
@@ -40,15 +46,15 @@
[alert addSubview:alertAvatarScrollView]; [alert addSubview:alertAvatarScrollView];
CGPoint selectedOffset = CGPointZero; CGPoint selectedOffset = CGPointZero;
for (int a = 0; a < MPAvatarCount; ++a) { for (NSUInteger a = 0; a < MPAvatarCount; ++a) {
UIButton *avatar = [self.avatarTemplate cloneAddedTo:alertAvatarScrollView]; UIButton *avatar = [self.avatarTemplate cloneAddedTo:alertAvatarScrollView];
avatar.tag = a; avatar.tag = (NSInteger)a;
avatar.hidden = NO; avatar.hidden = NO;
avatar.center = CGPointMake( avatar.center = CGPointMake(
(20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a, (20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
20 + self.avatarTemplate.bounds.size.height / 2 ); 20 + self.avatarTemplate.bounds.size.height / 2 );
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%d", a )] forState:UIControlStateNormal]; [avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%ld", (long)a )] forState:UIControlStateNormal];
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO]; [avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
avatar.layer.cornerRadius = avatar.bounds.size.height / 2; avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
@@ -96,11 +102,7 @@
UILabel *alertNameLabel = [self.nameLabel cloneAddedTo:container]; UILabel *alertNameLabel = [self.nameLabel cloneAddedTo:container];
alertNameLabel.center = alertAvatar.center; alertNameLabel.center = alertAvatar.center;
alertNameLabel.text = user.name; alertNameLabel.text = user.name;
alertNameLabel.bounds = CGRectSetHeight( alertNameLabel.bounds, [alertNameLabel sizeToFit];
[alertNameLabel.text sizeWithFont:self.nameLabel.font
constrainedToSize:CGSizeMake( alertNameLabel.bounds.size.width - 10,
100 )
lineBreakMode:self.nameLabel.lineBreakMode].height );
alertNameLabel.layer.cornerRadius = 5; alertNameLabel.layer.cornerRadius = 5;
alertNameLabel.backgroundColor = [UIColor blackColor]; alertNameLabel.backgroundColor = [UIColor blackColor];
} }
@@ -122,6 +124,7 @@
- (void)viewDidLoad { - (void)viewDidLoad {
self.gitTipButton.iTunesID = [MPConfig get].iTunesID;
self.avatarToUserOID = [NSMutableDictionary dictionaryWithCapacity:3]; self.avatarToUserOID = [NSMutableDictionary dictionaryWithCapacity:3];
[self.avatarsView addGestureRecognizer:self.targetedUserActionGesture]; [self.avatarsView addGestureRecognizer:self.targetedUserActionGesture];
@@ -136,12 +139,12 @@
self.emergencyGeneratorContainer.alpha = 0; self.emergencyGeneratorContainer.alpha = 0;
self.emergencyGeneratorContainer.hidden = YES; self.emergencyGeneratorContainer.hidden = YES;
self.emergencyQueue = [NSOperationQueue new]; self.emergencyQueue = [NSOperationQueue new];
[self.emergencyCounterStepper addTargetBlock:^(id sender, UIControlEvents event) { [self.emergencyCounterStepper addTargetBlock:^(id sender, UIControlEvents event, id weakSelf) {
self.emergencyCounter.text = PearlString( @"%lu", (unsigned long)self.emergencyCounterStepper.value ); self.emergencyCounter.text = PearlString( @"%lu", (unsigned long)self.emergencyCounterStepper.value );
[self updateEmergencyPassword]; [self updateEmergencyPassword];
} forControlEvents:UIControlEventValueChanged]; } forControlEvents:UIControlEventValueChanged];
[self.emergencyTypeControl addTargetBlock:^(id sender, UIControlEvents event) { [self.emergencyTypeControl addTargetBlock:^(id sender, UIControlEvents event, id weakSelf) {
[self updateEmergencyPassword]; [self updateEmergencyPassword];
} forControlEvents:UIControlEventValueChanged]; } forControlEvents:UIControlEventValueChanged];
self.emergencyContentTipContainer.alpha = 0; self.emergencyContentTipContainer.alpha = 0;
@@ -159,11 +162,11 @@
self.wordList = wordListLines; self.wordList = wordListLines;
self.wordWall.alpha = 0; self.wordWall.alpha = 0;
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [self.wordWall enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
UILabel *wordLabel = (UILabel *)subview; UILabel *wordLabel = (UILabel *)subview;
[self initializeWordLabel:wordLabel]; [self initializeWordLabel:wordLabel];
} recurse:NO]; } recurse:NO];
[[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil [[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
@@ -225,6 +228,7 @@
self.uiContainer.alpha = 0; self.uiContainer.alpha = 0;
self.shareContainer.alpha = 0; self.shareContainer.alpha = 0;
self.spinner.alpha = 0; self.spinner.alpha = 0;
self.tipsTipContainer.alpha = 0;
[super viewWillAppear:animated]; [super viewWillAppear:animated];
} }
@@ -240,10 +244,12 @@
[UIView animateWithDuration:1 animations:^{ [UIView animateWithDuration:1 animations:^{
self.uiContainer.alpha = 1; self.uiContainer.alpha = 1;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) if (finished)
[UIView animateWithDuration:1 animations:^{ [UIView animateWithDuration:1 animations:^{
self.shareContainer.alpha = 1; self.shareContainer.alpha = 1;
if ([MPConfig get].firstVersionRun)
self.tipsTipContainer.alpha = 1;
}]; }];
}]; }];
@@ -266,12 +272,6 @@
[super viewWillDisappear: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 { - (BOOL)prefersStatusBarHidden {
return YES; return YES;
@@ -282,6 +282,12 @@
return UIStatusBarAnimationSlide; return UIStatusBarAnimationSlide;
} }
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"MP_Settings"])
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
- (BOOL)canBecomeFirstResponder { - (BOOL)canBecomeFirstResponder {
return YES; return YES;
@@ -310,6 +316,17 @@
- (void)updateUsers { - (void)updateUsers {
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
if (mainContext) {
if (self.mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self.mocObserver];
self.mocObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
queue:nil usingBlock:^(NSNotification *note) {
[self updateUsers];
}];
}
[MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSError *error = nil; NSError *error = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
@@ -347,7 +364,7 @@
avatar.layer.shadowRadius = 20; avatar.layer.shadowRadius = 20;
avatar.layer.masksToBounds = NO; avatar.layer.masksToBounds = NO;
avatar.backgroundColor = [UIColor clearColor]; avatar.backgroundColor = [UIColor clearColor];
avatar.tag = user.avatar; avatar.tag = (NSInteger)user.avatar;
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%lu", (unsigned long)user.avatar )] [avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%lu", (unsigned long)user.avatar )]
forState:UIControlStateNormal]; forState:UIControlStateNormal];
@@ -367,7 +384,6 @@
[self didSelectNewUserAvatar:avatar]; [self didSelectNewUserAvatar:avatar];
else if ([self setSelectedUser:user]) else if ([self setSelectedUser:user])
[self didToggleUserSelection]; [self didToggleUserSelection];
} options:0]; } options:0];
(self.avatarToUserOID)[[NSValue valueWithNonretainedObject:avatar]] = NilToNSNull([user objectID]); (self.avatarToUserOID)[[NSValue valueWithNonretainedObject:avatar]] = NilToNSNull([user objectID]);
@@ -400,8 +416,7 @@
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar { - (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] ) MPUserEntity *newUser = [MPUserEntity insertNewObjectInContext:context];
inManagedObjectContext:context];
[self showNewUserNameAlertFor:newUser saveInContext:context completion:^(BOOL finished) { [self showNewUserNameAlertFor:newUser saveInContext:context completion:^(BOOL finished) {
newUserAvatar.selected = NO; newUserAvatar.selected = NO;
@@ -432,7 +447,7 @@
[PearlAlert showAlertWithTitle:@"Name Is Required" message:nil viewStyle:UIAlertViewStyleDefault initAlert:nil [PearlAlert showAlertWithTitle:@"Name Is Required" message:nil viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
[self showNewUserNameAlertFor:newUser saveInContext:context completion:completion]; [self showNewUserNameAlertFor:newUser saveInContext:context completion:completion];
} cancelTitle:@"Try Again" otherTitles:nil]; } cancelTitle:@"Try Again" otherTitles:nil];
return; return;
} }
@@ -457,7 +472,7 @@
// Okay // Okay
[self showNewUserConfirmationAlertFor:newUser saveInContext:context completion:completion]; [self showNewUserConfirmationAlertFor:newUser saveInContext:context completion:completion];
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil]; } cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
} }
- (void)showNewUserConfirmationAlertFor:(MPUserEntity *)newUser saveInContext:(NSManagedObjectContext *)context - (void)showNewUserConfirmationAlertFor:(MPUserEntity *)newUser saveInContext:(NSManagedObjectContext *)context
@@ -570,7 +585,7 @@
targetedUser = [self userForAvatar:targetedAvatar inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; targetedUser = [self userForAvatar:targetedAvatar inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
} }
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [self.avatarsView enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if (![[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]]) if (![[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
// This subview is not one of the user avatars. // This subview is not one of the user avatars.
return; return;
@@ -579,10 +594,10 @@
BOOL isTargeted = avatar == targetedAvatar; BOOL isTargeted = avatar == targetedAvatar;
avatar.userInteractionEnabled = isTargeted; avatar.userInteractionEnabled = isTargeted;
avatar.alpha = isTargeted? 1: [self selectedUserForThread]? 0.1: 0.4; avatar.alpha = isTargeted? 1: [self selectedUserForThread]? 0.1F: 0.4F;
[self updateAvatarShadowColor:avatar isTargeted:isTargeted]; [self updateAvatarShadowColor:avatar isTargeted:isTargeted];
} recurse:NO]; } recurse:NO];
if (allowScroll) { if (allowScroll) {
CGPoint targetContentOffset = CGPointMake( CGPoint targetContentOffset = CGPointMake(
@@ -594,10 +609,7 @@
// Lay out user name label. // Lay out user name label.
self.nameLabel.text = targetedAvatar? (targetedUser? targetedUser.name: @"New User"): nil; self.nameLabel.text = targetedAvatar? (targetedUser? targetedUser.name: @"New User"): nil;
self.nameLabel.bounds = CGRectSetHeight( self.nameLabel.bounds, [self.nameLabel sizeToFit];
[self.nameLabel.text sizeWithFont:self.nameLabel.font
constrainedToSize:CGSizeMake( self.nameLabel.bounds.size.width - 10, 100 )
lineBreakMode:self.nameLabel.lineBreakMode].height );
self.oldNameLabel.bounds = self.nameLabel.bounds; self.oldNameLabel.bounds = self.nameLabel.bounds;
if (completion) if (completion)
completion( YES ); completion( YES );
@@ -605,22 +617,22 @@
- (void)beginWordWallAnimation { - (void)beginWordWallAnimation {
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [self.wordWall enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
UILabel *wordLabel = (UILabel *)subview; UILabel *wordLabel = (UILabel *)subview;
if (wordLabel.frame.origin.x < -self.wordWall.frame.size.width / 3) { if (wordLabel.frame.origin.x < -self.wordWall.frame.size.width / 3) {
wordLabel.frame = CGRectSetX( wordLabel.frame, wordLabel.frame.origin.x + self.wordWall.frame.size.width ); wordLabel.frame = CGRectSetX( wordLabel.frame, wordLabel.frame.origin.x + self.wordWall.frame.size.width );
[self initializeWordLabel:wordLabel]; [self initializeWordLabel:wordLabel];
} }
} recurse:NO]; } recurse:NO];
if (self.wordWallAnimating) if (self.wordWallAnimating)
[UIView animateWithDuration:15 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ [UIView animateWithDuration:15 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [self.wordWall enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
UILabel *wordLabel = (UILabel *)subview; UILabel *wordLabel = (UILabel *)subview;
wordLabel.frame = CGRectSetX( wordLabel.frame, wordLabel.frame.origin.x - self.wordWall.frame.size.width / 3 ); wordLabel.frame = CGRectSetX( wordLabel.frame, wordLabel.frame.origin.x - self.wordWall.frame.size.width / 3 );
} recurse:NO]; } recurse:NO];
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (finished) if (finished)
[self beginWordWallAnimation]; [self beginWordWallAnimation];
@@ -629,7 +641,7 @@
- (void)initializeWordLabel:(UILabel *)wordLabel { - (void)initializeWordLabel:(UILabel *)wordLabel {
wordLabel.alpha = 0.05 + (random() % 35) / 100.0F; wordLabel.alpha = 0.05F + (random() % 35) / 100.0F;
wordLabel.text = (self.wordList)[(NSUInteger)random() % [self.wordList count]]; wordLabel.text = (self.wordList)[(NSUInteger)random() % [self.wordList count]];
} }
@@ -705,7 +717,7 @@
- (void)setSpinnerActive:(BOOL)active { - (void)setSpinnerActive:(BOOL)active {
PearlMainThread(^{ PearlMainQueue( ^{
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.toValue = [NSNumber numberWithDouble:2 * M_PI]; rotate.toValue = [NSNumber numberWithDouble:2 * M_PI];
rotate.duration = 5.0; rotate.duration = 5.0;
@@ -731,7 +743,7 @@
else else
[self avatarForUser:[self selectedUserForThread]].backgroundColor = self.avatarTemplate.backgroundColor; [self avatarForUser:[self selectedUserForThread]].backgroundColor = self.avatarTemplate.backgroundColor;
}]; }];
}); } );
} }
- (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted { - (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted {
@@ -740,7 +752,7 @@
if (![avatar.layer animationForKey:@"targetedShadow"]) { if (![avatar.layer animationForKey:@"targetedShadow"]) {
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"]; CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
toShadowColorAnimation.toValue = (__bridge id)(avatar.selected? self.avatarTemplate.backgroundColor toShadowColorAnimation.toValue = (__bridge id)(avatar.selected? self.avatarTemplate.backgroundColor
: [UIColor whiteColor]).CGColor; : [UIColor whiteColor]).CGColor;
toShadowColorAnimation.beginTime = 0.0f; toShadowColorAnimation.beginTime = 0.0f;
toShadowColorAnimation.duration = 0.5f; toShadowColorAnimation.duration = 0.5f;
toShadowColorAnimation.fillMode = kCAFillModeForwards; toShadowColorAnimation.fillMode = kCAFillModeForwards;
@@ -1166,7 +1178,7 @@
} }
if (buttonIndex == [sheet firstOtherButtonIndex] + 3) { if (buttonIndex == [sheet firstOtherButtonIndex] + 3) {
// Mailing List // Mailing List
[PearlEMail sendEMailTo:@"masterpassword-join@lists.lyndir.com" subject:@"Subscribe" [PearlEMail sendEMailTo:@"masterpassword-join@lists.lyndir.com" fromVC:self subject:@"Subscribe"
body:@"Press 'Send' now to subscribe to the Master Password mailing list.\n\n" body:@"Press 'Send' now to subscribe to the Master Password mailing list.\n\n"
@"You'll be kept up-to-date on the evolution of and discussions revolving Master Password."]; @"You'll be kept up-to-date on the evolution of and discussions revolving Master Password."];
return; return;
@@ -1194,8 +1206,11 @@
NSError *error; NSError *error;
MPUserEntity *selectedUser = (MPUserEntity *)[moc existingObjectWithID:_selectedUserOID error:&error]; MPUserEntity *selectedUser = (MPUserEntity *)[moc existingObjectWithID:_selectedUserOID error:&error];
if (!selectedUser) if (!selectedUser) {
err(@"Failed to retrieve selected user: %@", error); err(@"Failed to retrieve selected user: %@", error);
_selectedUserOID = nil;
[self updateUsers];
}
return selectedUser; return selectedUser;
} }
@@ -1207,8 +1222,11 @@
NSError *error = nil; NSError *error = nil;
if (selectedUser.objectID.isTemporaryID && if (selectedUser.objectID.isTemporaryID &&
![selectedUser.managedObjectContext obtainPermanentIDsForObjects:@[ selectedUser ] error:&error]) ![selectedUser.managedObjectContext obtainPermanentIDsForObjects:@[ selectedUser ] error:&error]) {
err(@"Failed to obtain a permanent object ID after setting selected user: %@", error); err(@"Failed to obtain a permanent object ID after setting selected user: %@", error);
_selectedUserOID = nil;
[self updateUsers];
}
_selectedUserOID = selectedUser.objectID; _selectedUserOID = selectedUser.objectID;
return YES; return YES;

View File

@@ -0,0 +1,46 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCombinedViewController.h
// MPCombinedViewController
//
// Created by lhunath on 2014-03-08.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "LLGitTip.h"
@interface MPUsersViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UITextFieldDelegate>
@property (strong, nonatomic) IBOutlet UINavigationBar *navigationBar;
@property(weak, nonatomic) IBOutlet UIView *userSelectionContainer;
@property(weak, nonatomic) IBOutlet UILabel *hintLabel;
@property(weak, nonatomic) IBOutlet UIView *gitTipTip;
@property(weak, nonatomic) IBOutlet LLGitTip *gitTipButton;
@property(weak, nonatomic) IBOutlet UITextField *entryField;
@property(weak, nonatomic) IBOutlet UILabel *entryLabel;
@property(weak, nonatomic) IBOutlet UILabel *entryTipTitleLabel;
@property(weak, nonatomic) IBOutlet UILabel *entryTipSubtitleLabel;
@property(weak, nonatomic) IBOutlet UIView *entryTipContainer;
@property(weak, nonatomic) IBOutlet UIView *entryContainer;
@property(weak, nonatomic) IBOutlet UIView *footerContainer;
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *storeLoadingActivity;
@property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
@property (strong, nonatomic) IBOutlet UIButton *nextAvatarButton;
@property (strong, nonatomic) IBOutlet UIButton *previousAvatarButton;
@property(assign, nonatomic) BOOL active;
- (void)setActive:(BOOL)active animated:(BOOL)animated;
- (IBAction)changeAvatar:(UIButton *)sender;
@end

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