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"]
path = External/RHStatusItemView
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"]
path = External/DCIntrospect
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="OCUnusedMacroInspection" 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="UnusedLocalVariable" 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>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.1.3</string>
<string>2.1.7</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>13</string>
<string>26</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<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.
\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 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.
## Requirements
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
@@ -40,9 +39,9 @@ The AdSupport.framework is required for iOS 6.0+ in order to uniquely identify u
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.
@@ -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.
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).
@@ -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).
NB: Checkpoints are only recorded during sessions.
### Custom Environment Information
@@ -163,6 +166,8 @@ Which will produce output that looks like
-[MyAppDelegate application:didFinishLaunchingWithOptions:] [Line 45] Launched!
NB: Logs are only recorded during sessions.
**Custom Logging**
If you have your own custom logging, call `TFLog` from your custom logging function. If you do not need `TFLog` to log to the console or STDERR because you handle those yourself, you can turn them off with these calls:

View File

@@ -6,7 +6,7 @@
// Copyright 2011 TestFlight. All rights reserved.
#import <Foundation/Foundation.h>
#define TESTFLIGHT_SDK_VERSION @"2.0.0"
#define TESTFLIGHT_SDK_VERSION @"2.1.4"
#undef TFLog
#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.
* 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

View File

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

View File

View File

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

View File

@@ -29,30 +29,49 @@
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.lyndir.lhunath.masterpassword.CLI</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>prepare-package</id>
<phase>prepare-package</phase>
<configuration>
<target>
<chmod file="${project.build.directory}/install" perm="755"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.4</version>
<artifactId>maven-shade-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
<goal>shade</goal>
</goals>
<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>
</execution>
</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.
# export MP_PASSWORD="banana colored duckling"
cd "${BASH_SOURCE[0]%/*}"
java -jar masterpassword-cli-GIT-SNAPSHOT.jar "$@"
cd "%SHAREPATH%"
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>
<module>masterpassword-algorithm</module>
<module>masterpassword-cli</module>
<module>masterpassword-android</module>
<!--module>masterpassword-android</module-->
</modules>
<!-- REMOTE ARTIFACT REPOSITORIES -->

View File

@@ -37,6 +37,8 @@
- (NSString *)shortNameOfType:(MPElementType)type;
- (NSString *)classNameOfType:(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 *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;

View File

@@ -31,6 +31,21 @@
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 {
NSError *error = nil;
@@ -127,7 +142,7 @@
return @"Device Private Password";
}
Throw(@"Type not supported: %d", type);
Throw(@"Type not supported: %lu", (long)type);
}
- (NSString *)shortNameOfType:(MPElementType)type {
@@ -161,7 +176,7 @@
return @"Device";
}
Throw(@"Type not supported: %d", type);
Throw(@"Type not supported: %lu", (long)type);
}
- (NSString *)classNameOfType:(MPElementType)type {
@@ -200,7 +215,43 @@
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 {
@@ -214,23 +265,24 @@
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
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:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
nameLengthBytes,
[name dataUsingEncoding:NSUTF8StringEncoding],
counterBytes,
nil]
nameLengthBytes, [name dataUsingEncoding:NSUTF8StringEncoding],
counterBytes, nil]
hmacWith:PearlHashSHA256 key:key.keyData];
trc(@"seed is: %@", [seed encodeBase64]);
const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte.
NSAssert([seed length], @"Missing seed.");
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]]
valueForKey:[self nameOfType:type]];
NSString *typeClass = [self classNameOfType: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]];
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.
NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher.");
@@ -239,8 +291,7 @@
uint16_t keyByte = htons(seedBytes[c + 1]);
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length],
1 )];
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
[content appendString:character];
@@ -264,13 +315,13 @@
case MPElementTypeGeneratedBasic:
case MPElementTypeGeneratedShort:
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;
}
case MPElementTypeStoredPersonal: {
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]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
@@ -279,7 +330,7 @@
}
case MPElementTypeStoredDevicePrivate: {
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]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
@@ -324,7 +375,7 @@
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN: {
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;
MPElementType type = element.type;
@@ -346,7 +397,7 @@
case MPElementTypeStoredPersonal: {
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;
@@ -358,7 +409,7 @@
}
case MPElementTypeStoredDevicePrivate: {
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];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
@@ -387,7 +438,7 @@
case MPElementTypeStoredPersonal: {
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])
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
@@ -445,7 +496,7 @@
case MPElementTypeStoredPersonal: {
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];
break;
}

View File

@@ -75,6 +75,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if (password)
NSAssert(![NSThread isMainThread], @"Computing key must not happen from the main thread.");
if (!user)
return NO;
MPKey *tryKey = nil;
@@ -166,7 +168,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
MPKey *recoverKey = newKey;
#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
for (MPElementEntity *element in user.elements) {

View File

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

View File

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

View File

@@ -21,6 +21,8 @@ typedef enum {
@interface MPAppDelegate_Shared(Store)<UbiquityStoreManagerDelegate>
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
+ (BOOL)managedObjectContextForMainThreadPerformBlock:(void (^)(NSManagedObjectContext *mainContext))mocBlock;
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void (^)(NSManagedObjectContext *mainContext))mocBlock;
+ (BOOL)managedObjectContextPerformBlock:(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. */
- (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
askImportPassword:(NSString *(^)(NSString *userName))importPassword
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords;
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords;
@end

View File

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

View File

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

View File

@@ -8,13 +8,14 @@
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
@implementation NSManagedObjectContext(MP)
- (BOOL)saveToStore {
__block BOOL success = YES;
if ([self hasChanges])
if ([self hasChanges]) {
[self performBlockAndWait:^{
@try {
NSError *error = nil;
@@ -26,6 +27,7 @@
err(@"While saving: %@", exception);
}
}];
}
return success && (!self.parentContext || [self.parentContext saveToStore]);
}
@@ -42,6 +44,14 @@
type = [self.user defaultType];
if (!type || type == (MPElementType)NSNotFound)
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;
}
@@ -53,6 +63,8 @@
aType = [self.user defaultType];
if (!aType || aType == (MPElementType)NSNotFound)
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);
}
@@ -125,8 +137,8 @@
- (NSString *)debugDescription {
return PearlString( @"{%@: name=%@, user=%@, type=%d, 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,
return PearlString( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed, (long)self.version,
self.loginName, self.requiresExplicitMigration );
}
@@ -143,6 +155,16 @@
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
@implementation MPElementGeneratedEntity(MP)
@@ -187,7 +209,7 @@
- (MPElementType)defaultType {
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
return IfElse((MPElementType)[self.defaultType_ unsignedIntegerValue], MPElementTypeGeneratedLong);
}
- (void)setDefaultType:(MPElementType)aDefaultType {

View File

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

View File

@@ -14,6 +14,12 @@
#define LOGIN_HELPER_BUNDLE_ID @"com.lyndir.lhunath.MasterPassword.Mac.LoginHelper"
@interface UbiquityStoreManager (Private)
- (void)markCloudStoreCorrupted;
@end
@interface MPMacAppDelegate()
@property(nonatomic, strong) NSWindowController *initialWindow;
@@ -129,16 +135,23 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)togglePreference:(id)sender {
if (sender == self.enableCloudButton)
[self storeManager].cloudEnabled = (self.enableCloudButton.state == NSOnState);
if (sender == self.enableCloudButton) {
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)
[self storeManager].cloudEnabled = !(self.useCloudItem.state == NSOnState);
[self storeManager].cloudEnabled = self.useCloudItem.state != NSOnState;
if (sender == self.rememberPasswordItem)
[MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]];
if (sender == self.openAtLoginButton)
[self setLoginItemEnabled:(self.openAtLoginButton.state == NSOnState)];
[self setLoginItemEnabled:self.openAtLoginButton.state == NSOnState];
if (sender == self.openAtLoginItem)
[self setLoginItemEnabled:!(self.openAtLoginItem.state == NSOnState)];
[self setLoginItemEnabled:self.openAtLoginItem.state != NSOnState];
if (sender == self.savePasswordItem) {
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
@@ -194,14 +207,24 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)rebuildCloud:(id)sender {
if ([[NSAlert alertWithMessageText:@"iCloud Truth Sync" defaultButton:@"Continue"
if ([[NSAlert alertWithMessageText:@"iCloud Truth Push" defaultButton:@"Continue"
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. "
"Any data on other devices not available from here will be lost."] runModal] == NSAlertDefaultReturn)
[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 {
[self.passwordWindow close];
@@ -301,8 +324,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
// Initial display.
[NSApp activateIgnoringOtherApps:YES];
if ([[MPMacConfig get].firstRun boolValue])
[self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self] showWindow:self];
if ([[MPMacConfig get].firstRun boolValue]) {
self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self];
[self.initialWindow.window setLevel:NSFloatingWindowLevel];
[self.initialWindow showWindow:self];
}
}
- (void)setActiveUser:(MPUserEntity *)activeUser {

View File

@@ -7,16 +7,17 @@
//
#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 *contentField;
@property(nonatomic, weak) IBOutlet NSTextField *tipField;
@property(nonatomic, weak) IBOutlet NSComboBox *typeField;
@property(nonatomic, weak) IBOutlet NSView *contentContainer;
@property(nonatomic, weak) IBOutlet NSProgressIndicator *progressView;
@property(nonatomic, weak) IBOutlet NSTextField *userLabel;
@property(nonatomic, weak) IBOutlet NSCollectionView *siteCollectionView;
- (void)updateElements;
@end

View File

@@ -10,44 +10,40 @@
#import "MPMacAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "MPElementModel.h"
#define MPAlertUnlockMP @"MPAlertUnlockMP"
#define MPAlertIncorrectMP @"MPAlertIncorrectMP"
#define MPAlertCreateSite @"MPAlertCreateSite"
#define MPAlertChangeType @"MPAlertChangeType"
@interface MPPasswordWindowController()
@property(nonatomic) BOOL inProgress;
@property(nonatomic) BOOL siteFieldPreventCompletion;
@property(nonatomic, strong) NSOperationQueue *backgroundQueue;
@property(nonatomic, strong) NSAlert *loadingDataAlert;
@property(nonatomic) BOOL closing;
@end
@implementation MPPasswordWindowController {
NSManagedObjectID *_activeElementOID;
}
@implementation MPPasswordWindowController
#pragma mark - Life
- (void)windowDidLoad {
if ([[MPMacConfig get].dialogStyleHUD boolValue]) {
self.window.styleMask = NSHUDWindowMask | NSTitledWindowMask | NSUtilityWindowMask | NSClosableWindowMask;
self.siteLabel.textColor = [NSColor whiteColor];
self.userLabel.textColor = [NSColor whiteColor];
}
else {
self.window.styleMask = NSTexturedBackgroundWindowMask | NSResizableWindowMask | NSTitledWindowMask | NSClosableWindowMask;
self.siteLabel.textColor = [NSColor controlTextColor];
self.userLabel.textColor = [NSColor controlTextColor];
}
self.backgroundQueue = [NSOperationQueue new];
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 managedObjectContextPerformBlock:^(NSManagedObjectContext *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."];
// [moc saveToStore];
// }];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES];
});
}];
} forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO];
[self.siteField selectText:nil];
}];
^(NSNotification *note) {
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO];
[self.siteField selectText:nil];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
NSWindow *sheet = [self.window attachedSheet];
if (sheet)
[NSApp endSheet:sheet];
^(NSNotification *note) {
NSWindow *sheet = [self.window attachedSheet];
if (sheet)
[NSApp endSheet:sheet];
[NSApp hide:nil];
self.closing = NO;
}];
[NSApp hide:nil];
self.closing = NO;
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
_activeElementOID = nil;
[self.siteField setStringValue:@""];
[self.typeField deselectItemAtIndex:[self.typeField indexOfSelectedItem]];
[self trySiteWithAction:NO];
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES];
}];
^(NSNotification *note) {
self.userLabel.stringValue = @"";
self.siteField.stringValue = @"";
self.elements = nil;
[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
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO];
}];
^(NSNotification *note) {
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO];
}];
[super windowDidLoad];
}
@@ -101,6 +103,152 @@
[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 {
if (![self ensureStoreLoaded])
@@ -163,188 +311,35 @@
if ([MPMacAppDelegate get].key)
return;
self.content = @"";
[self.siteField setStringValue:@""];
[self.typeField deselectItemAtIndex:[self.typeField indexOfSelectedItem]];
[self.tipField setStringValue:@""];
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel"
informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@",
userName];
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Unlock"];
[alert addButtonWithTitle:@"Change"];
[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 )];
[alert setAccessoryView:passwordField];
[alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP];
[passwordField becomeFirstResponder];
}];
}];
return unlocked;
}
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
- (void)updateElements {
if (contextInfo == MPAlertIncorrectMP) {
[self close];
NSString *query = [self.siteField.currentEditor string];
if (![query length] || ![MPMacAppDelegate get].key) {
self.elements = nil;
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) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]];
@@ -353,162 +348,77 @@
NSError *error = nil;
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
if (!siteResults)
err(@"While fetching elements for completion: %@", error);
else if ([siteResults count]) {
_activeElementOID = ((NSManagedObject *)[siteResults objectAtIndex:0]).objectID;
for (MPElementEntity *element in siteResults)
[mutableResults addObject:element.name];
if (!siteResults) {
err(@"While fetching elements for completion: %@", error);
return;
}
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.
[self close];
return YES;
}
if ((self.siteFieldPreventCompletion = [NSStringFromSelector( commandSelector ) hasPrefix:@"delete"])) { // Backspace any time.
_activeElementOID = nil;
[self trySiteWithAction:NO];
return NO;
}
if (commandSelector == @selector(insertNewline:)) { // Return without completion.
[self trySiteWithAction:YES];
return YES;
}
if (!self.elementSelectionIndexes)
return NSNotFound;
NSUInteger selectedIndex = self.elementSelectionIndexes.firstIndex;
if (selectedIndex >= self.elements.count)
return NSNotFound;
return NO;
return selectedIndex;
}
- (void)controlTextDidEndEditing:(NSNotification *)note {
- (NSBox *)selectedView {
if (note.object != self.siteField)
return;
[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)
NSUInteger selectedIndex = [self selectedIndex];
if (selectedIndex == NSNotFound)
return nil;
NSError *error;
MPElementEntity *activeElement = (MPElementEntity *)[moc existingObjectWithID:_activeElementOID error:&error];
if (!activeElement)
err(@"Couldn't retrieve active element: %@", error);
return activeElement;
return (NSBox *)[self.siteCollectionView itemAtIndex:selectedIndex].view;
}
- (void)setContent:(NSString *)content {
- (MPElementModel *)selectedElement {
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
paragraph.alignment = NSCenterTextAlignment;
NSUInteger selectedIndex = [self selectedIndex];
if (selectedIndex == NSNotFound)
return nil;
[self.contentField setAttributedStringValue:[[NSAttributedString alloc] initWithString:content attributes:@{
NSParagraphStyleAttributeName : paragraph
}]];
return (MPElementModel *)self.elements[selectedIndex];
}
- (void)trySiteWithAction:(BOOL)doAction {
- (void)setSelectedElement:(MPElementModel *)element {
NSString *siteName = [self.siteField stringValue];
[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 = @"";
self.elementSelectionIndexes = [NSIndexSet indexSetWithIndex:[self.elements indexOfObject:element]];
}
if (doAction) {
if ([content length]) {
// Performing action while content is available. Copy it.
[self copyContent:content];
}
else if ([siteName length]) {
- (void)useSite {
MPElementModel *selectedElement = [self selectedElement];
if (selectedElement) {
// Performing action while content is available. Copy it.
[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.
[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];
} );
}
}];
}];
[self createNewSite:siteName];
}
}
- (void)copyContent:(NSString *)content {
@@ -520,7 +430,7 @@
}
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
[[self activeElementInContext:moc] use];
[[self.selectedElement entityInContext:moc] use];
[moc saveToStore];
}];
}
@@ -528,12 +438,38 @@
- (void)createNewSite:(NSString *)siteName {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSAlert *alert = [NSAlert alertWithMessageText:@"Create site?"
defaultButton:@"Create" alternateButton:nil otherButton:@"Cancel"
informativeTextWithFormat:@"Do you want to create a new site named:\n\n%@", siteName];
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Create"];
[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
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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -10,8 +10,9 @@
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 */; };
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 */; };
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 */; };
DA0933D01747B91B00DE1CEF /* appstore.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CF1747B91B00DE1CEF /* appstore.png */; };
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 */; };
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
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 */; };
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.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 */; };
DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
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 */; };
DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5C981724A667003798D8 /* MPAlgorithm.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 */; };
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 */; };
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 */; };
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 */; };
@@ -221,15 +301,25 @@
/* Begin PBXFileReference section */
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>"; };
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>"; };
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>"; };
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>"; };
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; };
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>"; };
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>"; };
@@ -245,15 +335,6 @@
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; };
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>"; };
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>"; };
@@ -373,6 +454,87 @@
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>"; };
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; };
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>"; };
@@ -530,28 +692,12 @@
DA5E5C7917248AA1003798D8 /* lib */ = {
isa = PBXGroup;
children = (
DA5E5C7A17248AA1003798D8 /* include */,
DAEB938518AB0FFD000490CC /* include */,
DA5E5C8717248AA1003798D8 /* libscryptenc-osx.a */,
);
path = lib;
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 */ = {
isa = PBXGroup;
children = (
@@ -608,6 +754,10 @@
DA5E5CC41724A667003798D8 /* MainMenu.xib */,
DA5E5CC61724A667003798D8 /* main.m */,
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */,
93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */,
93D3960D320FF8A072B092E3 /* MPElementCollectionView.h */,
93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */,
93D39240B5143E01F0B75E96 /* MPElementModel.h */,
);
path = Mac;
sourceTree = "<group>";
@@ -793,9 +943,119 @@
name = Products;
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 */ = {
isa = PBXGroup;
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 */,
DA3509FD15F101A500C14A8E /* PearlQueue.m */,
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */,
@@ -896,42 +1156,117 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
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 */,
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 */,
DAEB93F518AB0FFD000490CC /* evp.h in Headers */,
DAEB941918AB0FFD000490CC /* ts.h in Headers */,
DAEB93F818AB0FFD000490CC /* krb5_asn.h in Headers */,
DAFE4A1915039824003ABA7C /* Pearl.h in Headers */,
DAEB93F318AB0FFD000490CC /* engine.h in Headers */,
DAFE4A1A15039824003ABA7C /* PearlAbstractStrings.h in Headers */,
DAFE4A1E15039824003ABA7C /* PearlCodeUtils.h in Headers */,
DAEB940918AB0FFD000490CC /* pqueue.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 */,
DAEB93E518AB0FFD000490CC /* conf.h in Headers */,
DAFE4A2415039824003ABA7C /* PearlInfoPlist.h in Headers */,
DAEB940618AB0FFD000490CC /* pem2.h in Headers */,
DAFE4A2615039824003ABA7C /* PearlLogger.h in Headers */,
DA2CA4F018D323D3007798F8 /* NSArray+Pearl.h in Headers */,
DAEB93FC18AB0FFD000490CC /* md5.h in Headers */,
DAFE4A2815039824003ABA7C /* PearlMathUtils.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 */,
DAEB940B18AB0FFD000490CC /* rc2.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 */,
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 */,
DAEB941C18AB0FFD000490CC /* ui_compat.h in Headers */,
DAEB93E218AB0FFD000490CC /* cast.h in Headers */,
DAEB942318AB0FFD000490CC /* memlimit.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 */,
DAEB941518AB0FFD000490CC /* ssl3.h in Headers */,
DAEB941618AB0FFD000490CC /* stack.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 */,
DAEB93D918AB0FFD000490CC /* aes.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 */,
DAEB93EC18AB0FFD000490CC /* dso.h in Headers */,
DAEB940118AB0FFD000490CC /* ocsp.h in Headers */,
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.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 */,
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 */,
DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */,
DA5E5C8817248AA1003798D8 /* crypto_aesctr.h in Headers */,
DA5E5C8917248AA1003798D8 /* crypto_scrypt.h in Headers */,
DA5E5C8A17248AA1003798D8 /* memlimit.h in Headers */,
DA5E5C8B17248AA1003798D8 /* readpass.h in Headers */,
DA5E5C8C17248AA1003798D8 /* scryptenc.h in Headers */,
DA5E5C8D17248AA1003798D8 /* scryptenc_cpuperf.h in Headers */,
DA5E5C8E17248AA1003798D8 /* sha256.h in Headers */,
DA5E5C8F17248AA1003798D8 /* sysendian.h in Headers */,
DA5E5C9017248AA1003798D8 /* warn.h in Headers */,
DAEB942918AB0FFD000490CC /* warn.h in Headers */,
DAEB93DB18AB0FFD000490CC /* asn1_mac.h in Headers */,
DAEB940518AB0FFD000490CC /* pem.h in Headers */,
DAEB942818AB0FFD000490CC /* sysendian.h in Headers */,
DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */,
DAEB93FF18AB0FFD000490CC /* obj_mac.h in Headers */,
DAEB93E718AB0FFD000490CC /* crypto.h in Headers */,
DAEB941318AB0FFD000490CC /* ssl2.h in Headers */,
DAEB940D18AB0FFD000490CC /* ripemd.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1138,6 +1473,7 @@
DACA27231705DF81002C6C22 /* avatar-15@2x.png in Resources */,
DACA27241705DF81002C6C22 /* avatar-5@2x.png in Resources */,
DACA27251705DF81002C6C22 /* avatar-6.png in Resources */,
DAEB942E18B47FB3000490CC /* MPInitialWindow.xib in Resources */,
DACA27261705DF81002C6C22 /* avatar-6@2x.png in Resources */,
DACA27271705DF81002C6C22 /* avatar-16@2x.png in Resources */,
DACA27281705DF81002C6C22 /* avatar-10.png in Resources */,
@@ -1172,7 +1508,6 @@
DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */,
DA5E5D0B1724A667003798D8 /* MainMenu.xib in Resources */,
DA5E5D551724F9C8003798D8 /* MasterPassword.iconset in Resources */,
DA0933CA1747A56A00DE1CEF /* MPInitialWindow.xib in Resources */,
DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */,
DA0933D01747B91B00DE1CEF /* appstore.png in Resources */,
);
@@ -1265,6 +1600,8 @@
DA5E5D051724A667003798D8 /* MPPasswordWindowController.m in Sources */,
DA5E5D0C1724A667003798D8 /* main.m in Sources */,
DA5E5D0D1724A667003798D8 /* MasterPassword.xcdatamodeld in Sources */,
93D39C7C2BE7C0E0763B0177 /* MPElementCollectionView.m in Sources */,
93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1291,6 +1628,7 @@
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */,
DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */,
DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */,
DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */,
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */,
DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */,
DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */,
@@ -1306,8 +1644,10 @@
DAFE4A3915039824003ABA7C /* PearlRSAKey.m in Sources */,
DAFE4A3B15039824003ABA7C /* PearlSCrypt.m in Sources */,
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */,
DA2CA4F118D323D3007798F8 /* NSTimer+PearlBlock.m in Sources */,
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */,
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */,
DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */,
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */,
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */,
DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */,
@@ -1578,7 +1918,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application";
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
FRAMEWORK_SEARCH_PATHS = (
@@ -1644,7 +1984,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES;
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist";
@@ -1659,7 +1998,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES;
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist";
@@ -1689,7 +2027,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application";
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
FRAMEWORK_SEARCH_PATHS = (
@@ -1755,7 +2093,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES;
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist";

View File

@@ -4,11 +4,11 @@
<dict>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.Mac</string>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
<string>HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.Mac</string>
<string>HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared</string>
</array>
<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>
<true/>
</dict>

View File

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

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;
[[MPiOSAppDelegate get] addElementNamed:[alert textFieldAtIndex:0].text completion:^(MPElementEntity *element) {
if (element) {
[wSelf.delegate didSelectElement:element];
[wSelf close:nil];
}
if (element)
PearlMainQueue( ^{
[wSelf.delegate didSelectElement:element];
[wSelf close:nil];
} );
}];
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonOkay, nil];
@@ -85,7 +86,7 @@
if (buttonIndex == [alert cancelButtonIndex])
return;
PearlOverlay *activity = [PearlOverlay showOverlayWithTitle:@"Upgrading Sites"];
PearlOverlay *activity = [PearlOverlay showProgressOverlayWithTitle:@"Upgrading Sites"];
[self performUpgradeAllWithCompletion:^(BOOL success, NSDictionary *changes) {
dispatch_async( dispatch_get_main_queue(), ^{
[self showUpgradeChanges:changes];
@@ -149,7 +150,7 @@
if (buttonIndex == [alert cancelButtonIndex])
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];
}

View File

@@ -105,7 +105,7 @@
UISearchBar *searchBar = self.searchDisplayController.searchBar;
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]] &&
CGPointEqualToPoint(
@@ -118,7 +118,7 @@
*stop = YES;
}
} recurse:NO];
} recurse:NO];
}
- (BOOL)newSiteSectionNeeded {
@@ -193,7 +193,7 @@
NSString *query = [self.searchDisplayController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
cell.textLabel.text = query;
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 {
@@ -220,7 +220,9 @@
__weak MPElementListController *wSelf = self;
[[MPiOSAppDelegate get] addElementNamed:siteName completion:^(MPElementEntity *element) {
if (element)
[wSelf.delegate didSelectElement:element];
PearlMainQueue( ^{
[wSelf.delegate didSelectElement:element];
} );
}];
} 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>
@property(weak, nonatomic) IBOutlet UISearchBar *searchBar;
@property(weak, nonatomic) IBOutlet UIView *siteNameTip;
@property(weak, nonatomic) IBOutlet UIView *contentTip;
@property(weak, nonatomic) IBOutlet UILabel *contentTipText;

View File

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

View File

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

View File

@@ -715,11 +715,6 @@
[self setHelpHidden:NO animated:YES];
break;
}
case 1: {
inf(@"Action: Guide");
[[MPiOSAppDelegate get] showGuide];
break;
}
case 2: {
inf(@"Action: Preferences");
[self performSegueWithIdentifier:@"MP_UserProfile" sender:self];
@@ -776,7 +771,7 @@
@"If you continue, the password for this site will change. "
@"You will need to update your account's old password to the new one."
do:^BOOL(MPElementEntity *activeElement, NSManagedObjectContext *context) {
_activeElementOID = [[MPiOSAppDelegate get] changeElement:activeElement inContext:context
_activeElementOID = [[MPiOSAppDelegate get] changeElement:activeElement saveInContext:context
toType:type].objectID;
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 "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 UITableViewCell *feedbackCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *coachmarksCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *exportCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *changeMPCell;
@property(weak, nonatomic) IBOutlet UILabel *defaultTypeLabel;
@property(weak, nonatomic) IBOutlet UIImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UISegmentedControl *typeControl;
- (IBAction)didToggleSwitch:(UISwitch *)sender;
- (IBAction)previousAvatar:(id)sender;
- (IBAction)nextAvatar:(id)sender;
- (IBAction)valueChanged:(id)sender;
@end

View File

@@ -6,12 +6,13 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import "MPPreferencesViewController.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "UIColor+Expanded.h"
#import "MPPasswordsViewController.h"
#import "MPCoachmarkViewController.h"
@interface MPPreferencesViewController()
@@ -21,158 +22,146 @@
- (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];
self.view.backgroundColor = [UIColor clearColor];
}
- (void)viewWillAppear:(BOOL)animated {
inf(@"Preferences will appear");
[self.avatarsView autoSizeContent];
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if (subview.tag && ((UIControl *)subview).selected) {
[self.avatarsView setContentOffset:CGPointMake( subview.center.x - self.avatarsView.bounds.size.width / 2, 0 )
animated:animated];
}
} recurse:NO];
[super viewWillAppear:animated];
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.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;
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
}
#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 {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell == self.feedbackCell)
[[MPiOSAppDelegate get] showFeedbackWithLogs:YES forVC:self];
if (cell == self.exportCell)
[[MPiOSAppDelegate get] export];
else if (cell == self.changeMPCell) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
[[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil];
}];
[[MPiOSAppDelegate get] showExportForVC:self];
if (cell == self.coachmarksCell) {
for (UIViewController *vc = self; (vc = vc.parentViewController); )
if ([vc isKindOfClass:[MPPasswordsViewController class]]) {
MPPasswordsViewController *passwordsVC = (MPPasswordsViewController *)vc;
passwordsVC.coachmark.coached = NO;
[passwordsVC dismissPopdown:self];
[vc performSegueWithIdentifier:@"coachmarks" sender:self];
}
}
[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 {
- (IBAction)valueChanged:(id)sender {
if (sender == self.savePasswordSwitch)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
if ((activeUser.saveKey = sender.on))
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
if ((activeUser.saveKey = self.savePasswordSwitch.on))
[[MPiOSAppDelegate get] storeSavedKeyFor:activeUser];
else
[[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

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];
}
- (IBAction)showGuide:(UIBarButtonItem *)sender {
[MPiOSConfig get].showSetup = @NO;
[self dismissViewControllerAnimated:YES completion:^{
[[MPiOSAppDelegate get] showGuide];
}];
}
@end

View File

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

View File

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