2
0

User support.

[ADDED]     Support for multiple users, each their own master password.
This commit is contained in:
Maarten Billemont 2012-06-04 11:27:02 +02:00
parent 3de9a0c67e
commit ba299d4674
80 changed files with 1835 additions and 586 deletions

View File

@ -0,0 +1,8 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
<option name="myLocal" value="false" />
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SeveralTargetsMessage" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit a72579ea27c797698ddd9546a407578fe8b62003 Subproject commit f3e1830d55b29e840a58fde8a62d58ed4b5a6b86

View File

@ -12,6 +12,7 @@
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; }; DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; };
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; }; DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; };
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; }; DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; };
DA0E07961577FE490008A67E /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0E07951577FE490008A67E /* MPEntities.m */; };
DA30E9CE15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h */; }; DA30E9CE15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h */; };
DA30E9CF15722ECA00A68B4C /* NSBundle_PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle_PearlMutableInfo.m */; }; DA30E9CF15722ECA00A68B4C /* NSBundle_PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle_PearlMutableInfo.m */; };
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; }; DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
@ -36,6 +37,44 @@
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA600C2715056427008E9AB6 /* MPConfig.m */; }; DA600C2815056428008E9AB6 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA600C2715056427008E9AB6 /* MPConfig.m */; };
DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; }; DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; };
DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
DA7DEFE8157978AC009D2085 /* avatar-female-1.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFC2157978AC009D2085 /* avatar-female-1.png */; };
DA7DEFE9157978AC009D2085 /* avatar-female-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFC3157978AC009D2085 /* avatar-female-1@2x.png */; };
DA7DEFEA157978AC009D2085 /* avatar-female-2.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFC4157978AC009D2085 /* avatar-female-2.png */; };
DA7DEFEB157978AC009D2085 /* avatar-female-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFC5157978AC009D2085 /* avatar-female-2@2x.png */; };
DA7DEFEC157978AC009D2085 /* avatar-female-silhouette-1.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFC6157978AC009D2085 /* avatar-female-silhouette-1.png */; };
DA7DEFED157978AC009D2085 /* avatar-female-silhouette-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFC7157978AC009D2085 /* avatar-female-silhouette-1@2x.png */; };
DA7DEFEE157978AC009D2085 /* avatar-female-silhouette-2.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFC8157978AC009D2085 /* avatar-female-silhouette-2.png */; };
DA7DEFEF157978AC009D2085 /* avatar-female-silhouette-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFC9157978AC009D2085 /* avatar-female-silhouette-2@2x.png */; };
DA7DEFF0157978AC009D2085 /* avatar-female-silhouette-3.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFCA157978AC009D2085 /* avatar-female-silhouette-3.png */; };
DA7DEFF1157978AC009D2085 /* avatar-female-silhouette-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFCB157978AC009D2085 /* avatar-female-silhouette-3@2x.png */; };
DA7DEFF2157978AC009D2085 /* avatar-female-silhouette-4.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFCC157978AC009D2085 /* avatar-female-silhouette-4.png */; };
DA7DEFF3157978AC009D2085 /* avatar-female-silhouette-4@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFCD157978AC009D2085 /* avatar-female-silhouette-4@2x.png */; };
DA7DEFF4157978AC009D2085 /* avatar-female-silhouette-5.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFCE157978AC009D2085 /* avatar-female-silhouette-5.png */; };
DA7DEFF5157978AC009D2085 /* avatar-female-silhouette-5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFCF157978AC009D2085 /* avatar-female-silhouette-5@2x.png */; };
DA7DEFF6157978AC009D2085 /* avatar-female-silhouette-6.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD0157978AC009D2085 /* avatar-female-silhouette-6.png */; };
DA7DEFF7157978AC009D2085 /* avatar-female-silhouette-6@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD1157978AC009D2085 /* avatar-female-silhouette-6@2x.png */; };
DA7DEFF8157978AC009D2085 /* avatar-female-silhouette-7.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD2157978AC009D2085 /* avatar-female-silhouette-7.png */; };
DA7DEFF9157978AC009D2085 /* avatar-female-silhouette-7@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD3157978AC009D2085 /* avatar-female-silhouette-7@2x.png */; };
DA7DEFFA157978AC009D2085 /* avatar-male-1.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD4157978AC009D2085 /* avatar-male-1.png */; };
DA7DEFFB157978AC009D2085 /* avatar-male-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD5157978AC009D2085 /* avatar-male-1@2x.png */; };
DA7DEFFC157978AC009D2085 /* avatar-male-2.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD6157978AC009D2085 /* avatar-male-2.png */; };
DA7DEFFD157978AC009D2085 /* avatar-male-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD7157978AC009D2085 /* avatar-male-2@2x.png */; };
DA7DEFFE157978AC009D2085 /* avatar-male-3.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD8157978AC009D2085 /* avatar-male-3.png */; };
DA7DEFFF157978AC009D2085 /* avatar-male-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFD9157978AC009D2085 /* avatar-male-3@2x.png */; };
DA7DF000157978AC009D2085 /* avatar-male-silhouette-1.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFDA157978AC009D2085 /* avatar-male-silhouette-1.png */; };
DA7DF001157978AC009D2085 /* avatar-male-silhouette-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFDB157978AC009D2085 /* avatar-male-silhouette-1@2x.png */; };
DA7DF002157978AC009D2085 /* avatar-male-silhouette-2.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFDC157978AC009D2085 /* avatar-male-silhouette-2.png */; };
DA7DF003157978AC009D2085 /* avatar-male-silhouette-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFDD157978AC009D2085 /* avatar-male-silhouette-2@2x.png */; };
DA7DF004157978AC009D2085 /* avatar-male-silhouette-3.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFDE157978AC009D2085 /* avatar-male-silhouette-3.png */; };
DA7DF005157978AC009D2085 /* avatar-male-silhouette-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFDF157978AC009D2085 /* avatar-male-silhouette-3@2x.png */; };
DA7DF006157978AC009D2085 /* avatar-male-silhouette-4.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFE0157978AC009D2085 /* avatar-male-silhouette-4.png */; };
DA7DF007157978AC009D2085 /* avatar-male-silhouette-4@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFE1157978AC009D2085 /* avatar-male-silhouette-4@2x.png */; };
DA7DF008157978AC009D2085 /* avatar-male-silhouette-5.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFE2157978AC009D2085 /* avatar-male-silhouette-5.png */; };
DA7DF009157978AC009D2085 /* avatar-male-silhouette-5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFE3157978AC009D2085 /* avatar-male-silhouette-5@2x.png */; };
DA7DF00A157978AC009D2085 /* avatar-male-silhouette-6.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFE4157978AC009D2085 /* avatar-male-silhouette-6.png */; };
DA7DF00B157978AC009D2085 /* avatar-male-silhouette-6@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFE5157978AC009D2085 /* avatar-male-silhouette-6@2x.png */; };
DA7DF00C157978AC009D2085 /* avatar-male-silhouette-7.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFE6157978AC009D2085 /* avatar-male-silhouette-7.png */; };
DA7DF00D157978AC009D2085 /* avatar-male-silhouette-7@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7DEFE7157978AC009D2085 /* avatar-male-silhouette-7@2x.png */; };
DA95D59D14DF063C008D1B94 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DA95D59D14DF063C008D1B94 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA95D5CF14DF0691008D1B94 /* IASKAppSettingsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */; }; DA95D5CF14DF0691008D1B94 /* IASKAppSettingsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */; };
DA95D5D014DF0691008D1B94 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */; }; DA95D5D014DF0691008D1B94 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */; };
@ -91,10 +130,7 @@
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; }; DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; }; DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; }; DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; };
DAB8D46B15036BCF00CED3BC /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */; };
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45615036BCF00CED3BC /* MPTypes.m */; }; DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45615036BCF00CED3BC /* MPTypes.m */; };
DAB8D46D15036BCF00CED3BC /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45815036BCF00CED3BC /* MPElementEntity.m */; };
DAB8D46E15036BCF00CED3BC /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */; };
DAB8D6FA15036BF600CED3BC /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47115036BF600CED3BC /* ui_background.png */; }; DAB8D6FA15036BF600CED3BC /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47115036BF600CED3BC /* ui_background.png */; };
DAB8D6FB15036BF600CED3BC /* ui_background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47215036BF600CED3BC /* ui_background@2x.png */; }; DAB8D6FB15036BF600CED3BC /* ui_background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47215036BF600CED3BC /* ui_background@2x.png */; };
DAB8D6FC15036BF600CED3BC /* ui_box_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47315036BF600CED3BC /* ui_box_checked.png */; }; DAB8D6FC15036BF600CED3BC /* ui_box_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47315036BF600CED3BC /* ui_box_checked.png */; };
@ -672,6 +708,11 @@
DAC6327B1486809A0075AEA5 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC632791486809A0075AEA5 /* JRSwizzle.h */; }; DAC6327B1486809A0075AEA5 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC632791486809A0075AEA5 /* JRSwizzle.h */; };
DAC6327C1486809A0075AEA5 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC6327A1486809A0075AEA5 /* JRSwizzle.m */; }; DAC6327C1486809A0075AEA5 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC6327A1486809A0075AEA5 /* JRSwizzle.m */; };
DAC632891486D9690075AEA5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; }; DAC632891486D9690075AEA5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; };
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */; };
DAC728E4157CAE5A00889EF2 /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC728E3157CAE5A00889EF2 /* MPElementEntity.m */; };
DAC728E7157CAE5B00889EF2 /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC728E6157CAE5B00889EF2 /* MPUserEntity.m */; };
DAC728EA157CAE5B00889EF2 /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC728E9157CAE5B00889EF2 /* MPElementGeneratedEntity.m */; };
DAC728ED157CAE5B00889EF2 /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC728EC157CAE5B00889EF2 /* MPElementStoredEntity.m */; };
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DACABB8515729E80008BA211 /* ApptentiveResources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAAC35D6156BD51600C5FD93 /* ApptentiveResources.bundle */; }; DACABB8515729E80008BA211 /* ApptentiveResources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAAC35D6156BD51600C5FD93 /* ApptentiveResources.bundle */; };
DACABB861572A2A7008BA211 /* tip_alert_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */; }; DACABB861572A2A7008BA211 /* tip_alert_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6BA15036BF600CED3BC /* tip_alert_black.png */; };
@ -774,6 +815,13 @@
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAFE45FA15039823003ABA7C /* Pearl.strings */; }; DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAFE45FA15039823003ABA7C /* Pearl.strings */; };
DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */; }; DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */; };
DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */; }; DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */; };
DAFE4A63150399FF003ABA7E /* (null) in Sources */ = {isa = PBXBuildFile; };
DAFE4A63150399FF003ABA82 /* UIControl_PearlBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA81 /* UIControl_PearlBlocks.m */; };
DAFE4A63150399FF003ABA84 /* UIControl_PearlBlocks.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA83 /* UIControl_PearlBlocks.h */; };
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */; };
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */; };
DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */; };
DAFE4A63150399FF003ABA8C /* UIControl+PearlSelect.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -868,6 +916,8 @@
DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tip_arrow_wood.png; path = Resources/Tooltips/tip_arrow_wood.png; sourceTree = SOURCE_ROOT; }; DA0A1D0C15690AD40092735D /* tip_arrow_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tip_arrow_wood.png; path = Resources/Tooltips/tip_arrow_wood.png; sourceTree = SOURCE_ROOT; };
DA0A1D1315690AF30092735D /* Icon-72@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72@2x.png"; sourceTree = "<group>"; }; DA0A1D1315690AF30092735D /* Icon-72@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72@2x.png"; sourceTree = "<group>"; };
DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; }; DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
DA30E9CB15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSBundle_PearlMutableInfo.h; sourceTree = "<group>"; }; DA30E9CB15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSBundle_PearlMutableInfo.h; sourceTree = "<group>"; };
DA30E9CC15722ECA00A68B4C /* NSBundle_PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSBundle_PearlMutableInfo.m; sourceTree = "<group>"; }; DA30E9CC15722ECA00A68B4C /* NSBundle_PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSBundle_PearlMutableInfo.m; sourceTree = "<group>"; };
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; }; DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
@ -894,6 +944,45 @@
DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios.a"; sourceTree = "<group>"; }; DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios.a"; sourceTree = "<group>"; };
DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = "<group>"; }; DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = "<group>"; };
DA7DEFC2157978AC009D2085 /* avatar-female-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-1.png"; sourceTree = "<group>"; };
DA7DEFC3157978AC009D2085 /* avatar-female-1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-1@2x.png"; sourceTree = "<group>"; };
DA7DEFC4157978AC009D2085 /* avatar-female-2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-2.png"; sourceTree = "<group>"; };
DA7DEFC5157978AC009D2085 /* avatar-female-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-2@2x.png"; sourceTree = "<group>"; };
DA7DEFC6157978AC009D2085 /* avatar-female-silhouette-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-1.png"; sourceTree = "<group>"; };
DA7DEFC7157978AC009D2085 /* avatar-female-silhouette-1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-1@2x.png"; sourceTree = "<group>"; };
DA7DEFC8157978AC009D2085 /* avatar-female-silhouette-2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-2.png"; sourceTree = "<group>"; };
DA7DEFC9157978AC009D2085 /* avatar-female-silhouette-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-2@2x.png"; sourceTree = "<group>"; };
DA7DEFCA157978AC009D2085 /* avatar-female-silhouette-3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-3.png"; sourceTree = "<group>"; };
DA7DEFCB157978AC009D2085 /* avatar-female-silhouette-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-3@2x.png"; sourceTree = "<group>"; };
DA7DEFCC157978AC009D2085 /* avatar-female-silhouette-4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-4.png"; sourceTree = "<group>"; };
DA7DEFCD157978AC009D2085 /* avatar-female-silhouette-4@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-4@2x.png"; sourceTree = "<group>"; };
DA7DEFCE157978AC009D2085 /* avatar-female-silhouette-5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-5.png"; sourceTree = "<group>"; };
DA7DEFCF157978AC009D2085 /* avatar-female-silhouette-5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-5@2x.png"; sourceTree = "<group>"; };
DA7DEFD0157978AC009D2085 /* avatar-female-silhouette-6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-6.png"; sourceTree = "<group>"; };
DA7DEFD1157978AC009D2085 /* avatar-female-silhouette-6@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-6@2x.png"; sourceTree = "<group>"; };
DA7DEFD2157978AC009D2085 /* avatar-female-silhouette-7.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-7.png"; sourceTree = "<group>"; };
DA7DEFD3157978AC009D2085 /* avatar-female-silhouette-7@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-female-silhouette-7@2x.png"; sourceTree = "<group>"; };
DA7DEFD4157978AC009D2085 /* avatar-male-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-1.png"; sourceTree = "<group>"; };
DA7DEFD5157978AC009D2085 /* avatar-male-1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-1@2x.png"; sourceTree = "<group>"; };
DA7DEFD6157978AC009D2085 /* avatar-male-2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-2.png"; sourceTree = "<group>"; };
DA7DEFD7157978AC009D2085 /* avatar-male-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-2@2x.png"; sourceTree = "<group>"; };
DA7DEFD8157978AC009D2085 /* avatar-male-3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-3.png"; sourceTree = "<group>"; };
DA7DEFD9157978AC009D2085 /* avatar-male-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-3@2x.png"; sourceTree = "<group>"; };
DA7DEFDA157978AC009D2085 /* avatar-male-silhouette-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-1.png"; sourceTree = "<group>"; };
DA7DEFDB157978AC009D2085 /* avatar-male-silhouette-1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-1@2x.png"; sourceTree = "<group>"; };
DA7DEFDC157978AC009D2085 /* avatar-male-silhouette-2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-2.png"; sourceTree = "<group>"; };
DA7DEFDD157978AC009D2085 /* avatar-male-silhouette-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-2@2x.png"; sourceTree = "<group>"; };
DA7DEFDE157978AC009D2085 /* avatar-male-silhouette-3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-3.png"; sourceTree = "<group>"; };
DA7DEFDF157978AC009D2085 /* avatar-male-silhouette-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-3@2x.png"; sourceTree = "<group>"; };
DA7DEFE0157978AC009D2085 /* avatar-male-silhouette-4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-4.png"; sourceTree = "<group>"; };
DA7DEFE1157978AC009D2085 /* avatar-male-silhouette-4@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-4@2x.png"; sourceTree = "<group>"; };
DA7DEFE2157978AC009D2085 /* avatar-male-silhouette-5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-5.png"; sourceTree = "<group>"; };
DA7DEFE3157978AC009D2085 /* avatar-male-silhouette-5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-5@2x.png"; sourceTree = "<group>"; };
DA7DEFE4157978AC009D2085 /* avatar-male-silhouette-6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-6.png"; sourceTree = "<group>"; };
DA7DEFE5157978AC009D2085 /* avatar-male-silhouette-6@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-6@2x.png"; sourceTree = "<group>"; };
DA7DEFE6157978AC009D2085 /* avatar-male-silhouette-7.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-7.png"; sourceTree = "<group>"; };
DA7DEFE7157978AC009D2085 /* avatar-male-silhouette-7@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-male-silhouette-7@2x.png"; sourceTree = "<group>"; };
DA902BD01576CA4A00C38161 /* keypad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = keypad.png; sourceTree = "<group>"; };
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libInAppSettingsKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libInAppSettingsKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsViewController.h; sourceTree = "<group>"; }; DA95D5A814DF0691008D1B94 /* IASKAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsViewController.h; sourceTree = "<group>"; };
DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsViewController.m; sourceTree = "<group>"; }; DA95D5A914DF0691008D1B94 /* IASKAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsViewController.m; sourceTree = "<group>"; };
@ -959,14 +1048,8 @@
DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = "<group>"; }; DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = "<group>"; };
DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = "<group>"; }; DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = "<group>"; };
DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; }; DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; }; DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; };
DAB8D45715036BCF00CED3BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; }; DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; }; DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; }; DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
DAB8D47315036BF600CED3BC /* ui_box_checked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_box_checked.png; sourceTree = "<group>"; }; DAB8D47315036BF600CED3BC /* ui_box_checked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_box_checked.png; sourceTree = "<group>"; };
@ -1613,6 +1696,16 @@
DAC632791486809A0075AEA5 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = External/Pearl/External/jrswizzle/JRSwizzle.h; sourceTree = SOURCE_ROOT; }; DAC632791486809A0075AEA5 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = External/Pearl/External/jrswizzle/JRSwizzle.h; sourceTree = SOURCE_ROOT; };
DAC6327A1486809A0075AEA5 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JRSwizzle.m; path = External/Pearl/External/jrswizzle/JRSwizzle.m; sourceTree = SOURCE_ROOT; }; DAC6327A1486809A0075AEA5 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JRSwizzle.m; path = External/Pearl/External/jrswizzle/JRSwizzle.m; sourceTree = SOURCE_ROOT; };
DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
DAC728C8157C247B00889EF2 /* MPPreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPreferencesViewController.h; sourceTree = "<group>"; };
DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPreferencesViewController.m; sourceTree = "<group>"; };
DAC728E2157CAE5A00889EF2 /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DAC728E3157CAE5A00889EF2 /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DAC728E5157CAE5B00889EF2 /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DAC728E6157CAE5B00889EF2 /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DAC728E8157CAE5B00889EF2 /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DAC728E9157CAE5B00889EF2 /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DAC728EB157CAE5B00889EF2 /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DAC728EC157CAE5B00889EF2 /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; }; DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Pearl-Prefix.pch"; sourceTree = "<group>"; }; DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Pearl-Prefix.pch"; sourceTree = "<group>"; };
DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_top_right.png; sourceTree = "<group>"; }; DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_top_right.png; sourceTree = "<group>"; };
@ -1714,6 +1807,12 @@
DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImage_PearlScaling.m; sourceTree = "<group>"; }; DAFE4A1215039824003ABA7C /* UIImage_PearlScaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImage_PearlScaling.m; sourceTree = "<group>"; };
DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlAppDelegate.m; sourceTree = "<group>"; }; DAFE4A60150399FF003ABA7C /* PearlAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlAppDelegate.m; sourceTree = "<group>"; };
DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlAppDelegate.h; sourceTree = "<group>"; }; DAFE4A61150399FF003ABA7C /* PearlAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlAppDelegate.h; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA81 /* UIControl_PearlBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIControl_PearlBlocks.m; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA83 /* UIControl_PearlBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIControl_PearlBlocks.h; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlKVO.m"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlKVO.h"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+PearlSelect.m"; sourceTree = "<group>"; };
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+PearlSelect.h"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -1866,7 +1965,16 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */, DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
DAB8D43E15036BCF00CED3BC /* iOS */, DAC728EB157CAE5B00889EF2 /* MPElementStoredEntity.h */,
DAC728EC157CAE5B00889EF2 /* MPElementStoredEntity.m */,
DAC728E8157CAE5B00889EF2 /* MPElementGeneratedEntity.h */,
DAC728E9157CAE5B00889EF2 /* MPElementGeneratedEntity.m */,
DAC728E5157CAE5B00889EF2 /* MPUserEntity.h */,
DAC728E6157CAE5B00889EF2 /* MPUserEntity.m */,
DAC728E2157CAE5A00889EF2 /* MPElementEntity.h */,
DAC728E3157CAE5A00889EF2 /* MPElementEntity.m */,
DA0E07941577FE490008A67E /* MPEntities.h */,
DA0E07951577FE490008A67E /* MPEntities.m */,
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */, DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */, DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */, DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
@ -1875,14 +1983,9 @@
DA4426071557C1990052177D /* MPAppDelegate_Store.m */, DA4426071557C1990052177D /* MPAppDelegate_Store.m */,
DA600C2615056427008E9AB6 /* MPConfig.h */, DA600C2615056427008E9AB6 /* MPConfig.h */,
DA600C2715056427008E9AB6 /* MPConfig.m */, DA600C2715056427008E9AB6 /* MPConfig.m */,
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */,
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */,
DAB8D45915036BCF00CED3BC /* MPTypes.h */, DAB8D45915036BCF00CED3BC /* MPTypes.h */,
DAB8D45615036BCF00CED3BC /* MPTypes.m */, DAB8D45615036BCF00CED3BC /* MPTypes.m */,
DAB8D45715036BCF00CED3BC /* MPElementEntity.h */, DAB8D43E15036BCF00CED3BC /* iOS */,
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */,
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */,
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */,
); );
path = MasterPassword; path = MasterPassword;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1897,6 +2000,51 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DA902B931576C0FB00C38161 /* Avatars */ = {
isa = PBXGroup;
children = (
DA7DEFC2157978AC009D2085 /* avatar-female-1.png */,
DA7DEFC3157978AC009D2085 /* avatar-female-1@2x.png */,
DA7DEFC4157978AC009D2085 /* avatar-female-2.png */,
DA7DEFC5157978AC009D2085 /* avatar-female-2@2x.png */,
DA7DEFC6157978AC009D2085 /* avatar-female-silhouette-1.png */,
DA7DEFC7157978AC009D2085 /* avatar-female-silhouette-1@2x.png */,
DA7DEFC8157978AC009D2085 /* avatar-female-silhouette-2.png */,
DA7DEFC9157978AC009D2085 /* avatar-female-silhouette-2@2x.png */,
DA7DEFCA157978AC009D2085 /* avatar-female-silhouette-3.png */,
DA7DEFCB157978AC009D2085 /* avatar-female-silhouette-3@2x.png */,
DA7DEFCC157978AC009D2085 /* avatar-female-silhouette-4.png */,
DA7DEFCD157978AC009D2085 /* avatar-female-silhouette-4@2x.png */,
DA7DEFCE157978AC009D2085 /* avatar-female-silhouette-5.png */,
DA7DEFCF157978AC009D2085 /* avatar-female-silhouette-5@2x.png */,
DA7DEFD0157978AC009D2085 /* avatar-female-silhouette-6.png */,
DA7DEFD1157978AC009D2085 /* avatar-female-silhouette-6@2x.png */,
DA7DEFD2157978AC009D2085 /* avatar-female-silhouette-7.png */,
DA7DEFD3157978AC009D2085 /* avatar-female-silhouette-7@2x.png */,
DA7DEFD4157978AC009D2085 /* avatar-male-1.png */,
DA7DEFD5157978AC009D2085 /* avatar-male-1@2x.png */,
DA7DEFD6157978AC009D2085 /* avatar-male-2.png */,
DA7DEFD7157978AC009D2085 /* avatar-male-2@2x.png */,
DA7DEFD8157978AC009D2085 /* avatar-male-3.png */,
DA7DEFD9157978AC009D2085 /* avatar-male-3@2x.png */,
DA7DEFDA157978AC009D2085 /* avatar-male-silhouette-1.png */,
DA7DEFDB157978AC009D2085 /* avatar-male-silhouette-1@2x.png */,
DA7DEFDC157978AC009D2085 /* avatar-male-silhouette-2.png */,
DA7DEFDD157978AC009D2085 /* avatar-male-silhouette-2@2x.png */,
DA7DEFDE157978AC009D2085 /* avatar-male-silhouette-3.png */,
DA7DEFDF157978AC009D2085 /* avatar-male-silhouette-3@2x.png */,
DA7DEFE0157978AC009D2085 /* avatar-male-silhouette-4.png */,
DA7DEFE1157978AC009D2085 /* avatar-male-silhouette-4@2x.png */,
DA7DEFE2157978AC009D2085 /* avatar-male-silhouette-5.png */,
DA7DEFE3157978AC009D2085 /* avatar-male-silhouette-5@2x.png */,
DA7DEFE4157978AC009D2085 /* avatar-male-silhouette-6.png */,
DA7DEFE5157978AC009D2085 /* avatar-male-silhouette-6@2x.png */,
DA7DEFE6157978AC009D2085 /* avatar-male-silhouette-7.png */,
DA7DEFE7157978AC009D2085 /* avatar-male-silhouette-7@2x.png */,
);
path = Avatars;
sourceTree = "<group>";
};
DA95D59E14DF063C008D1B94 /* InAppSettingsKit */ = { DA95D59E14DF063C008D1B94 /* InAppSettingsKit */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2009,6 +2157,8 @@
DAB8D44B15036BCF00CED3BC /* MPGuideViewController.m */, DAB8D44B15036BCF00CED3BC /* MPGuideViewController.m */,
DAB8D44C15036BCF00CED3BC /* MPMainViewController.h */, DAB8D44C15036BCF00CED3BC /* MPMainViewController.h */,
DAB8D44D15036BCF00CED3BC /* MPMainViewController.m */, DAB8D44D15036BCF00CED3BC /* MPMainViewController.m */,
DAC728C8157C247B00889EF2 /* MPPreferencesViewController.h */,
DAC728C9157C247B00889EF2 /* MPPreferencesViewController.m */,
DAB8D44E15036BCF00CED3BC /* MPSearchDelegate.h */, DAB8D44E15036BCF00CED3BC /* MPSearchDelegate.h */,
DAB8D44F15036BCF00CED3BC /* MPSearchDelegate.m */, DAB8D44F15036BCF00CED3BC /* MPSearchDelegate.m */,
DAB8D45015036BCF00CED3BC /* MPTypeViewController.h */, DAB8D45015036BCF00CED3BC /* MPTypeViewController.h */,
@ -2023,6 +2173,8 @@
DAB8D46F15036BF600CED3BC /* Resources */ = { DAB8D46F15036BF600CED3BC /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA902BD01576CA4A00C38161 /* keypad.png */,
DA902B931576C0FB00C38161 /* Avatars */,
DAB8D47015036BF600CED3BC /* Automaton */, DAB8D47015036BF600CED3BC /* Automaton */,
DAB8D4CE15036BF600CED3BC /* Background */, DAB8D4CE15036BF600CED3BC /* Background */,
DAB8D4D215036BF600CED3BC /* ciphers.plist */, DAB8D4D215036BF600CED3BC /* ciphers.plist */,
@ -2850,6 +3002,8 @@
DAFE45D715039823003ABA7C /* Pearl */ = { DAFE45D715039823003ABA7C /* Pearl */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */,
DAFE4A63150399FF003ABA85 /* NSObject+PearlKVO.m */,
DA30E9CB15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h */, DA30E9CB15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h */,
DA30E9CC15722ECA00A68B4C /* NSBundle_PearlMutableInfo.m */, DA30E9CC15722ECA00A68B4C /* NSBundle_PearlMutableInfo.m */,
DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */, DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */,
@ -2921,6 +3075,10 @@
DAFE460715039823003ABA7C /* Pearl-UIKit */ = { DAFE460715039823003ABA7C /* Pearl-UIKit */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */,
DAFE4A63150399FF003ABA89 /* UIControl+PearlSelect.m */,
DAFE4A63150399FF003ABA83 /* UIControl_PearlBlocks.h */,
DAFE4A63150399FF003ABA81 /* UIControl_PearlBlocks.m */,
DAFE460815039823003ABA7C /* Pearl-UIKit-Dependencies.h */, DAFE460815039823003ABA7C /* Pearl-UIKit-Dependencies.h */,
DAFE460915039823003ABA7C /* Pearl-UIKit.h */, DAFE460915039823003ABA7C /* Pearl-UIKit.h */,
DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */, DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */,
@ -3061,6 +3219,9 @@
DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */, DAFE4A63150399FF003ABA7C /* PearlAppDelegate.h in Headers */,
DA30E9CE15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h in Headers */, DA30E9CE15722ECA00A68B4C /* NSBundle_PearlMutableInfo.h in Headers */,
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */, DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */,
DAFE4A63150399FF003ABA84 /* UIControl_PearlBlocks.h in Headers */,
DAFE4A63150399FF003ABA88 /* NSObject+PearlKVO.h in Headers */,
DAFE4A63150399FF003ABA8C /* UIControl+PearlSelect.h in Headers */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -3898,6 +4059,44 @@
DACABB8D1572A4A5008BA211 /* tip_basic_black_top_right@2x.png in Resources */, DACABB8D1572A4A5008BA211 /* tip_basic_black_top_right@2x.png in Resources */,
DACABB901572B76A008BA211 /* tip_basic_black_top.png in Resources */, DACABB901572B76A008BA211 /* tip_basic_black_top.png in Resources */,
DACABB911572B76A008BA211 /* tip_basic_black_top@2x.png in Resources */, DACABB911572B76A008BA211 /* tip_basic_black_top@2x.png in Resources */,
DA7DEFE8157978AC009D2085 /* avatar-female-1.png in Resources */,
DA7DEFE9157978AC009D2085 /* avatar-female-1@2x.png in Resources */,
DA7DEFEA157978AC009D2085 /* avatar-female-2.png in Resources */,
DA7DEFEB157978AC009D2085 /* avatar-female-2@2x.png in Resources */,
DA7DEFEC157978AC009D2085 /* avatar-female-silhouette-1.png in Resources */,
DA7DEFED157978AC009D2085 /* avatar-female-silhouette-1@2x.png in Resources */,
DA7DEFEE157978AC009D2085 /* avatar-female-silhouette-2.png in Resources */,
DA7DEFEF157978AC009D2085 /* avatar-female-silhouette-2@2x.png in Resources */,
DA7DEFF0157978AC009D2085 /* avatar-female-silhouette-3.png in Resources */,
DA7DEFF1157978AC009D2085 /* avatar-female-silhouette-3@2x.png in Resources */,
DA7DEFF2157978AC009D2085 /* avatar-female-silhouette-4.png in Resources */,
DA7DEFF3157978AC009D2085 /* avatar-female-silhouette-4@2x.png in Resources */,
DA7DEFF4157978AC009D2085 /* avatar-female-silhouette-5.png in Resources */,
DA7DEFF5157978AC009D2085 /* avatar-female-silhouette-5@2x.png in Resources */,
DA7DEFF6157978AC009D2085 /* avatar-female-silhouette-6.png in Resources */,
DA7DEFF7157978AC009D2085 /* avatar-female-silhouette-6@2x.png in Resources */,
DA7DEFF8157978AC009D2085 /* avatar-female-silhouette-7.png in Resources */,
DA7DEFF9157978AC009D2085 /* avatar-female-silhouette-7@2x.png in Resources */,
DA7DEFFA157978AC009D2085 /* avatar-male-1.png in Resources */,
DA7DEFFB157978AC009D2085 /* avatar-male-1@2x.png in Resources */,
DA7DEFFC157978AC009D2085 /* avatar-male-2.png in Resources */,
DA7DEFFD157978AC009D2085 /* avatar-male-2@2x.png in Resources */,
DA7DEFFE157978AC009D2085 /* avatar-male-3.png in Resources */,
DA7DEFFF157978AC009D2085 /* avatar-male-3@2x.png in Resources */,
DA7DF000157978AC009D2085 /* avatar-male-silhouette-1.png in Resources */,
DA7DF001157978AC009D2085 /* avatar-male-silhouette-1@2x.png in Resources */,
DA7DF002157978AC009D2085 /* avatar-male-silhouette-2.png in Resources */,
DA7DF003157978AC009D2085 /* avatar-male-silhouette-2@2x.png in Resources */,
DA7DF004157978AC009D2085 /* avatar-male-silhouette-3.png in Resources */,
DA7DF005157978AC009D2085 /* avatar-male-silhouette-3@2x.png in Resources */,
DA7DF006157978AC009D2085 /* avatar-male-silhouette-4.png in Resources */,
DA7DF007157978AC009D2085 /* avatar-male-silhouette-4@2x.png in Resources */,
DA7DF008157978AC009D2085 /* avatar-male-silhouette-5.png in Resources */,
DA7DF009157978AC009D2085 /* avatar-male-silhouette-5@2x.png in Resources */,
DA7DF00A157978AC009D2085 /* avatar-male-silhouette-6.png in Resources */,
DA7DF00B157978AC009D2085 /* avatar-male-silhouette-6@2x.png in Resources */,
DA7DF00C157978AC009D2085 /* avatar-male-silhouette-7.png in Resources */,
DA7DF00D157978AC009D2085 /* avatar-male-silhouette-7@2x.png in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -3958,14 +4157,17 @@
DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */, DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */, DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */, DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
DAB8D46B15036BCF00CED3BC /* MPElementStoredEntity.m in Sources */,
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */, DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
DAB8D46D15036BCF00CED3BC /* MPElementEntity.m in Sources */,
DAB8D46E15036BCF00CED3BC /* MPElementGeneratedEntity.m in Sources */,
DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */, DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */, DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */, DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */, DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
DAC728E4157CAE5A00889EF2 /* MPElementEntity.m in Sources */,
DAC728E7157CAE5B00889EF2 /* MPUserEntity.m in Sources */,
DAC728EA157CAE5B00889EF2 /* MPElementGeneratedEntity.m in Sources */,
DAC728ED157CAE5B00889EF2 /* MPElementStoredEntity.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -4049,6 +4251,10 @@
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */, DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */,
DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */, DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */,
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */, DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */,
DAFE4A63150399FF003ABA7E /* (null) in Sources */,
DAFE4A63150399FF003ABA82 /* UIControl_PearlBlocks.m in Sources */,
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */,
DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -10,12 +10,13 @@
@interface MPAppDelegate_Shared (Key) @interface MPAppDelegate_Shared (Key)
- (void)loadStoredKey; - (void)loadSavedKey;
- (IBAction)signOut:(id)sender; - (IBAction)signOut:(id)sender;
- (BOOL)tryMasterPassword:(NSString *)tryPassword; - (BOOL)tryMasterPassword:(NSString *)tryPassword forUser:(MPUserEntity *)user;
- (void)updateKey:(NSData *)key; - (void)storeSavedKey;
- (void)forgetKey; - (void)forgetSavedKey;
- (void)unsetKey;
- (NSData *)keyWithLength:(NSUInteger)keyLength; - (NSData *)keyWithLength:(NSUInteger)keyLength;

View File

@ -8,67 +8,48 @@
#import "MPConfig.h" #import "MPConfig.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPElementEntity.h"
@implementation MPAppDelegate_Shared (Key) @implementation MPAppDelegate_Shared (Key)
static NSDictionary *keyQuery() { static NSDictionary *keyQuery(MPUserEntity *user) {
static NSDictionary *MPKeyQuery = nil; return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
if (!MPKeyQuery) attributes:[NSDictionary dictionaryWithObjectsAndKeys:
MPKeyQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword @"Saved Master Password", (__bridge id)kSecAttrService,
attributes:[NSDictionary dictionaryWithObjectsAndKeys: user.name, (__bridge id)kSecAttrAccount,
@"Saved Master Password", (__bridge id)kSecAttrService, nil]
@"default", (__bridge id)kSecAttrAccount, matches:nil];
nil]
matches:nil];
return MPKeyQuery;
} }
static NSDictionary *keyIDQuery() { - (void)forgetSavedKey {
static NSDictionary *MPKeyIDQuery = nil; if ([PearlKeyChain deleteItemForQuery:keyQuery(self.activeUser)] != errSecItemNotFound) {
if (!MPKeyIDQuery)
MPKeyIDQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"Master Password Check", (__bridge id)kSecAttrService,
@"default", (__bridge id)kSecAttrAccount,
nil]
matches:nil];
return MPKeyIDQuery;
}
- (void)forgetKey {
inf(@"Deleting key and ID from keychain.");
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
inf(@"Removed key from keychain."); inf(@"Removed key from keychain.");
if ([PearlKeyChain deleteItemForQuery:keyIDQuery()] != errSecItemNotFound)
inf(@"Removed key ID from keychain."); [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
#endif #endif
}
} }
- (IBAction)signOut:(id)sender { - (IBAction)signOut:(id)sender {
[MPConfig get].saveKey = [NSNumber numberWithBool:NO]; [self forgetSavedKey];
[self updateKey:nil]; [self unsetKey];
} }
- (void)loadStoredKey { - (void)loadSavedKey {
if ([[MPConfig get].saveKey boolValue]) { if ([self.activeUser.saveKey boolValue]) {
// Key is stored in keychain. Load it. // Key should be saved in keychain. Load it.
[self updateKey:[PearlKeyChain dataOfItemForQuery:keyQuery()]]; self.key = [PearlKeyChain dataOfItemForQuery:keyQuery(self.activeUser)];
inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing"); inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing");
if (self.key)
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
} else { } else {
// Key should not be stored in keychain. Delete it. // Key should not be stored in keychain. Delete it.
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound) if ([PearlKeyChain deleteItemForQuery:keyQuery(self.activeUser)] != errSecItemNotFound)
inf(@"Removed key from keychain."); inf(@"Removed key from keychain.");
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
@ -76,20 +57,19 @@ static NSDictionary *keyIDQuery() {
} }
} }
- (BOOL)tryMasterPassword:(NSString *)tryPassword { - (BOOL)tryMasterPassword:(NSString *)tryPassword forUser:(MPUserEntity *)user {
if (![tryPassword length]) if (![tryPassword length])
return NO; return NO;
NSData *tryKey = keyForPassword(tryPassword); NSData *tryKey = keyForPassword(tryPassword);
NSData *tryKeyID = keyIDForKey(tryKey); NSData *tryKeyID = keyIDForKey(tryKey);
NSData *keyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()]; inf(@"Key ID known? %@.", user.keyID? @"YES": @"NO");
inf(@"Key ID known? %@.", keyID? @"YES": @"NO"); if (user.keyID)
if (keyID)
// A key ID is known -> a password is set. // A key ID is known -> a password is set.
// Make sure the user's entered password matches it. // Make sure the user's entered password matches it.
if (![keyID isEqual:tryKeyID]) { if (![user.keyID isEqual:tryKeyID]) {
wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [keyID encodeHex], [tryKeyID encodeHex]); wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [user.keyID encodeHex], [tryKeyID encodeHex]);
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
@ -101,55 +81,45 @@ static NSDictionary *keyIDQuery() {
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered];
#endif #endif
[self updateKey:tryKey]; if (self.key != tryKey) {
self.key = tryKey;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
}
self.activeUser = user;
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
#endif
return YES; return YES;
} }
- (void)updateKey:(NSData *)key { - (void)storeSavedKey {
if (self.key != key) { if ([self.activeUser.saveKey boolValue]) {
self.key = key; NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(self.activeUser)];
if (key) if (![existingKey isEqualToData:self.key]) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self]; inf(@"Updating key in keychain.");
else [PearlKeyChain addOrUpdateItemForQuery:keyQuery(self.activeUser)
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
}
if (self.key) {
self.keyID = keyIDForKey(self.key);
NSData *existingKeyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
if (![existingKeyID isEqualToData:self.keyID]) {
inf(@"Updating key ID in keychain.");
[PearlKeyChain addOrUpdateItemForQuery:keyIDQuery()
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.keyID, (__bridge id)kSecValueData, self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif #endif
nil]]; nil]];
} }
if ([[MPConfig get].saveKey boolValue]) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery()];
if (![existingKey isEqualToData:self.key]) {
inf(@"Updating key in keychain.");
[PearlKeyChain addOrUpdateItemForQuery:keyQuery()
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
#endif
nil]];
}
}
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
#endif
} }
} }
- (void)unsetKey {
self.key = nil;
self.activeUser = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
}
- (NSData *)keyWithLength:(NSUInteger)keyLength { - (NSData *)keyWithLength:(NSUInteger)keyLength {
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))]; return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];

View File

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

View File

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

View File

@ -27,7 +27,6 @@ typedef enum {
- (UbiquityStoreManager *)storeManager; - (UbiquityStoreManager *)storeManager;
- (void)saveContext; - (void)saveContext;
- (void)printStore;
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password - (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation; askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation;

View File

@ -7,7 +7,7 @@
// //
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPElementEntity.h" #import "MPEntities.h"
#import "MPConfig.h" #import "MPConfig.h"
@implementation MPAppDelegate_Shared (Store) @implementation MPAppDelegate_Shared (Store)
@ -57,7 +57,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
} }
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// Start loading the store. // Start loading the store.
[self storeManager]; [self storeManager];
@ -69,7 +69,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
isReady = [self storeManager].isReady; isReady = [self storeManager].isReady;
}); });
} }
assert([self storeManager].isReady); assert([self storeManager].isReady);
return [self storeManager].persistentStoreCoordinator; return [self storeManager].persistentStoreCoordinator;
}]; }];
@ -134,51 +134,6 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
}]; }];
} }
- (void)printStore {
if (![self managedObjectModel] || ![self managedObjectContext]) {
trc(@"Not printing store: store not initialized.");
return;
}
[self.managedObjectContext performBlock:^{
trc(@"=== All entities ===");
for(NSEntityDescription *entity in [[self managedObjectModel] entities]) {
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:entity];
NSError *error;
NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:&error];
for(NSManagedObject *o in results) {
if ([o isKindOfClass:[MPElementEntity class]]) {
MPElementEntity *e = (MPElementEntity *)o;
trc(@"For descriptor: %@, found: %@: %@ (%@)", entity.name, [o class], e.name, e.keyID);
} else {
trc(@"For descriptor: %@, found: %@", entity.name, [o class]);
}
}
}
trc(@"---");
if ([MPAppDelegate_Shared get].keyID) {
trc(@"=== Known sites ===");
NSFetchRequest *fetchRequest = [[self managedObjectModel]
fetchRequestFromTemplateWithName:@"MPElements"
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
@"", @"query",
[MPAppDelegate_Shared get].keyID, @"keyID",
nil]];
[fetchRequest setSortDescriptors:
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
NSError *error = nil;
for (MPElementEntity *e in [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error]) {
trc(@"Found site: %@ (%@): %@", e.name, e.keyID, e);
}
trc(@"---");
} else
trc(@"Not printing sites: master password not set.");
}];
}
#pragma mark - UbiquityStoreManagerDelegate #pragma mark - UbiquityStoreManagerDelegate
- (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm { - (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm {
@ -278,7 +233,8 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
if (!headerPattern || !sitePattern) if (!headerPattern || !sitePattern)
return MPImportResultInternalError; return MPImportResultInternalError;
NSString *keyIDHex = nil; NSString *keyIDHex = nil, *userName = nil;
MPUserEntity *user = nil;
BOOL headerStarted = NO, headerEnded = NO; BOOL headerStarted = NO, headerEnded = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set]; NSMutableSet *elementsToDelete = [NSMutableSet set];
@ -307,6 +263,13 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject]; NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]]; NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]]; NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([key isEqualToString:@"User Name"]) {
userName = value;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
}
if ([key isEqualToString:@"Key ID"]) { if ([key isEqualToString:@"Key ID"]) {
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]]) if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]])
return MPImportResultInvalidPassword; return MPImportResultInvalidPassword;
@ -316,7 +279,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
} }
if (!headerEnded) if (!headerEnded)
continue; continue;
if (!keyIDHex) if (!keyIDHex || ![userName length])
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
if (![importedSiteLine length]) if (![importedSiteLine length])
continue; continue;
@ -334,15 +297,17 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
// Find existing site. // Find existing site.
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND keyID == %@", name, keyIDHex]; if (user) {
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
if (error) NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
err(@"Couldn't search existing sites: %@", error); if (error)
if (!existingSites) err(@"Couldn't search existing sites: %@", error);
return MPImportResultInternalError; if (!existingSites)
return MPImportResultInternalError;
[elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]]; [elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
}
} }
// Ask for confirmation to import these sites. // Ask for confirmation to import these sites.
@ -357,6 +322,11 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[self saveContext]; [self saveContext];
// Import new sites. // Import new sites.
if (!user) {
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) inManagedObjectContext:self.managedObjectContext];
user.name = userName;
user.keyID = [keyIDHex decodeHex];
}
for (NSArray *siteElements in importedSiteElements) { for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]]; NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]];
NSInteger uses = [[siteElements objectAtIndex:1] integerValue]; NSInteger uses = [[siteElements objectAtIndex:1] integerValue];
@ -365,14 +335,13 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
NSString *exportContent = [siteElements objectAtIndex:4]; NSString *exportContent = [siteElements objectAtIndex:4];
// Create new site. // Create new site.
inf(@"Importing site: name=%@, lastUsed=%@, uses=%d, type=%u, keyID=%@", name, lastUsed, uses, type, keyIDHex);
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:self.managedObjectContext]; inManagedObjectContext:self.managedObjectContext];
element.name = name; element.name = name;
element.keyID = [keyIDHex decodeHex]; element.user = user;
element.type = type; element.type = [NSNumber numberWithUnsignedInteger:type];
element.uses = uses; element.uses = [NSNumber numberWithUnsignedInteger:uses];
element.lastUsed = [lastUsed timeIntervalSinceReferenceDate]; element.lastUsed = lastUsed;
if ([exportContent length]) if ([exportContent length])
[element importContent:exportContent]; [element importContent:exportContent];
} }
@ -399,7 +368,8 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[export appendFormat:@"# \n"]; [export appendFormat:@"# \n"];
[export appendFormat:@"##\n"]; [export appendFormat:@"##\n"];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion]; [export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# Key ID: %@\n", [self.keyID encodeHex]]; [export appendFormat:@"# User Name: %@\n", self.activeUser.name];
[export appendFormat:@"# Key ID: %@\n", [self.activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]]; [export appendFormat:@"# Date: %@\n", [rfc3339DateFormatter stringFromDate:[NSDate date]]];
if (showPasswords) if (showPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"]; [export appendFormat:@"# Passwords: VISIBLE\n"];
@ -411,17 +381,9 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
[export appendFormat:@"# used used type name\tpassword\n"]; [export appendFormat:@"# used used type name\tpassword\n"];
// Sites. // Sites.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; for (MPElementEntity *element in self.activeUser.elements) {
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]; NSDate *lastUsed = element.lastUsed;
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", self.keyID]; NSNumber *uses = element.uses;
__autoreleasing NSError *error = nil;
NSArray *elements = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"Error fetching sites for export: %@", error);
for (MPElementEntity *element in elements) {
NSTimeInterval lastUsed = element.lastUsed;
int16_t uses = element.uses;
MPElementType type = (unsigned)element.type; MPElementType type = (unsigned)element.type;
NSString *name = element.name; NSString *name = element.name;
NSString *content = nil; NSString *content = nil;
@ -435,7 +397,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
} }
[export appendFormat:@"%@ %8d %8d %20s\t%@\n", [export appendFormat:@"%@ %8d %8d %20s\t%@\n",
[rfc3339DateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:lastUsed]], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content? content: @""]; [rfc3339DateFormatter stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content? content: @""];
} }
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION

View File

@ -8,8 +8,7 @@
@interface MPConfig : PearlConfig @interface MPConfig : PearlConfig
@property (nonatomic, retain) NSNumber *saveKey; @property (nonatomic, retain) NSNumber *rememberLogin;
@property (nonatomic, retain) NSNumber *rememberKey;
@property (nonatomic, retain) NSNumber *iCloud; @property (nonatomic, retain) NSNumber *iCloud;
@property (nonatomic, retain) NSNumber *iCloudDecided; @property (nonatomic, retain) NSNumber *iCloudDecided;

View File

@ -10,7 +10,7 @@
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
@implementation MPConfig @implementation MPConfig
@dynamic saveKey, rememberKey, iCloud, iCloudDecided; @dynamic rememberLogin, iCloud, iCloudDecided;
- (id)init { - (id)init {
@ -20,8 +20,7 @@
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: [self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)), [NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(saveKey)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKey)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
nil]]; nil]];

View File

@ -1,27 +1,23 @@
// //
// MPElementEntity.h // MPElementEntity.h
// MasterPassword // MasterPassword-iOS
// //
// Created by Maarten Billemont on 02/01/12. // Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <CoreData/CoreData.h> #import <CoreData/CoreData.h>
@class MPUserEntity;
@interface MPElementEntity : NSManagedObject @interface MPElementEntity : NSManagedObject
@property (nonatomic, retain) NSString *name; @property (nonatomic, retain) id content;
@property (nonatomic, retain) NSData *keyID; @property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, assign) int16_t type; @property (nonatomic, retain) NSString * name;
@property (nonatomic, assign) int16_t uses; @property (nonatomic, retain) NSNumber * type;
@property (nonatomic, assign) NSTimeInterval lastUsed; @property (nonatomic, retain) NSNumber * uses;
@property (nonatomic, retain) MPUserEntity *user;
@property (nonatomic, retain, readonly) id content;
- (int16_t)use;
- (NSString *)exportContent;
- (void)importContent:(NSString *)content;
@end @end

View File

@ -1,51 +1,22 @@
// //
// MPElementEntity.m // MPElementEntity.m
// MasterPassword // MasterPassword-iOS
// //
// Created by Maarten Billemont on 02/01/12. // Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPElementEntity.h" #import "MPElementEntity.h"
#import "MPUserEntity.h"
@implementation MPElementEntity @implementation MPElementEntity
@dynamic content;
@dynamic lastUsed;
@dynamic name; @dynamic name;
@dynamic keyID;
@dynamic type; @dynamic type;
@dynamic uses; @dynamic uses;
@dynamic lastUsed; @dynamic user;
- (int16_t)use {
self.lastUsed = [[NSDate date] timeIntervalSinceReferenceDate];
return ++self.uses;
}
- (id)content {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
}
- (NSString *)exportContent {
return nil;
}
- (void)importContent:(NSString *)content {
}
- (NSString *)description {
return PearlString(@"%@:%@", [self class], [self name]);
}
- (NSString *)debugDescription {
return PearlString(@"{%@: name=%@, keyID=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.keyID, self.type, self.uses, self.lastUsed);
}
@end @end

View File

@ -1,8 +1,8 @@
// //
// MPElementGeneratedEntity.h // MPElementGeneratedEntity.h
// MasterPassword // MasterPassword-iOS
// //
// Created by Maarten Billemont on 16/01/12. // Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity @interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, assign) int32_t counter; @property (nonatomic, retain) NSNumber * counter;
@end @end

View File

@ -1,31 +1,16 @@
// //
// MPElementGeneratedEntity.m // MPElementGeneratedEntity.m
// MasterPassword // MasterPassword-iOS
// //
// Created by Maarten Billemont on 16/01/12. // Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPElementGeneratedEntity.h" #import "MPElementGeneratedEntity.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
@implementation MPElementGeneratedEntity @implementation MPElementGeneratedEntity
@dynamic counter; @dynamic counter;
- (id)content {
if (!(self.type & MPElementTypeClassGenerated)) {
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
return nil;
}
if (![self.name length])
return nil;
return MPCalculateContent((unsigned)self.type, self.name, [MPAppDelegate get].key, self.counter);
}
@end @end

View File

@ -1,8 +1,8 @@
// //
// MPElementStoredEntity.h // MPElementStoredEntity.h
// MasterPassword // MasterPassword-iOS
// //
// Created by Maarten Billemont on 02/01/12. // Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@ -13,6 +13,6 @@
@interface MPElementStoredEntity : MPElementEntity @interface MPElementStoredEntity : MPElementEntity
@property (nonatomic, retain, readwrite) id content; @property (nonatomic, retain) id contentObject;
@end @end

View File

@ -1,76 +1,16 @@
// //
// MPElementStoredEntity.m // MPElementStoredEntity.m
// MasterPassword // MasterPassword-iOS
// //
// Created by Maarten Billemont on 02/01/12. // Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
#import "MPElementStoredEntity.h" #import "MPElementStoredEntity.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
@interface MPElementStoredEntity ()
@property (nonatomic, retain, readwrite) id contentObject;
@end
@implementation MPElementStoredEntity @implementation MPElementStoredEntity
@dynamic contentObject; @dynamic contentObject;
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount,
nil]
matches:nil];
}
- (id)content {
assert(self.type & MPElementTypeClassStored);
NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate)
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content {
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
nil]];
self.contentObject = nil;
} else
self.contentObject = encryptedContent;
}
- (NSString *)exportContent {
return [self.contentObject encodeBase64];
}
- (void)importContent:(NSString *)content {
self.contentObject = [content decodeBase64];
}
@end @end

View File

@ -0,0 +1,21 @@
//
// MPElementEntities.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPElementEntity.h"
#import "MPElementStoredEntity.h"
#import "MPElementGeneratedEntity.h"
#import "MPUserEntity.h"
@interface MPElementEntity (MP)
- (NSNumber *)use;
- (NSString *)exportContent;
- (void)importContent:(NSString *)content;
@end

122
MasterPassword/MPEntities.m Normal file
View File

@ -0,0 +1,122 @@
//
// MPElementEntities.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPEntities.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
@implementation MPElementEntity (MP)
- (NSNumber *)use {
self.lastUsed = [NSDate date];
self.uses = [NSNumber numberWithUnsignedInteger:[self.uses unsignedIntegerValue] + 1];
return self.uses;
}
- (id)content {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
}
- (NSString *)exportContent {
return nil;
}
- (void)importContent:(NSString *)content {
}
- (NSString *)description {
return PearlString(@"%@:%@", [self class], [self name]);
}
- (NSString *)debugDescription {
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed);
}
@end
@implementation MPElementGeneratedEntity (MP)
- (id)content {
if (!([self.type unsignedIntegerValue] & MPElementTypeClassGenerated)) {
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
return nil;
}
if (![self.name length])
return nil;
return MPCalculateContent([self.type unsignedIntegerValue], self.name, [MPAppDelegate get].key, [self.counter unsignedIntegerValue]);
}
@end
@implementation MPElementStoredEntity (MP)
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount,
nil]
matches:nil];
}
- (id)content {
assert([self.type unsignedIntegerValue] & MPElementTypeClassStored);
NSData *encryptedContent;
if ([self.type unsignedIntegerValue] & MPElementFeatureDevicePrivate)
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content {
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
if ([self.type unsignedIntegerValue] & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
nil]];
self.contentObject = nil;
} else
self.contentObject = encryptedContent;
}
- (NSString *)exportContent {
return [self.contentObject encodeBase64];
}
- (void)importContent:(NSString *)content {
self.contentObject = [content decodeBase64];
}
@end

View File

@ -83,4 +83,4 @@ NSData *keyIDForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type); NSString *NSStringFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type); NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type); Class ClassFromMPElementType(MPElementType type);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter); NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);

View File

@ -104,7 +104,7 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
} }
static NSDictionary *MPTypes_ciphers = nil; static NSDictionary *MPTypes_ciphers = nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter) { NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
if (!(type & MPElementTypeClassGenerated)) { if (!(type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name); err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
@ -118,7 +118,7 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, in
err(@"Key not set."); err(@"Key not set.");
return nil; return nil;
} }
uint32_t salt = (unsigned)counter; uint32_t salt = counter;
if (!counter) if (!counter)
// Counter unset, go into OTP mode. // Counter unset, go into OTP mode.
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in. // Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.

View File

@ -0,0 +1,31 @@
//
// MPUserEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPElementEntity;
@interface MPUserEntity : NSManagedObject
@property (nonatomic, retain) NSData * keyID;
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey;
@property (nonatomic, retain) NSNumber * avatar;
@property (nonatomic, retain) NSSet *elements;
@end
@interface MPUserEntity (CoreDataGeneratedAccessors)
- (void)addElementsObject:(MPElementEntity *)value;
- (void)removeElementsObject:(MPElementEntity *)value;
- (void)addElements:(NSSet *)values;
- (void)removeElements:(NSSet *)values;
@end

View File

@ -0,0 +1,22 @@
//
// MPUserEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPUserEntity.h"
#import "MPElementEntity.h"
@implementation MPUserEntity
@dynamic keyID;
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey;
@dynamic avatar;
@dynamic elements;
@end

View File

@ -115,8 +115,8 @@
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]; fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND keyID == %@", fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
query, query, [MPAppDelegate get].keyID]; query, query, [MPAppDelegate get].activeUser];
NSError *error = nil; NSError *error = nil;
self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error]; self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];

View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic"> <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES"> <entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<attribute name="keyID" attributeType="Binary" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/> <attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" syncable="YES"/> <attribute name="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/> <attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="16" syncable="YES"/> <attribute name="type" attributeType="Integer 16" defaultValueString="16" syncable="YES"/>
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/> <attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
</entity> </entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES"> <entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="counter" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/> <attribute name="counter" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
@ -13,9 +14,18 @@
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES"> <entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/> <attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity> </entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
<attribute name="avatar" optional="YES" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="saveKey" optional="YES" attributeType="Boolean" syncable="YES"/>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
</entity>
<elements> <elements>
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="120"/> <element name="MPElementEntity" positionX="160" positionY="192" width="128" height="120"/>
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/> <element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/> <element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="120"/>
</elements> </elements>
</model> </model>

View File

@ -62,20 +62,15 @@
if (!self.key) if (!self.key)
// Try and load the key from the keychain. // Try and load the key from the keychain.
[self loadStoredKey]; [self loadSavedKey];
if (!self.key) if (!self.key)
// Ask the user to set the key through his master password. // Ask the user to set the key through his master password.
if ([NSThread isMainThread]) PearlMainThread(^{
[self.navigationController presentViewController: [self.navigationController presentViewController:
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"] [self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil]; animated:animated completion:nil];
else });
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController presentViewController:
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil];
});
} }
- (void)export { - (void)export {
@ -91,7 +86,7 @@
@"making the result safe from falling in the wrong hands.\n\n" @"making the result safe from falling in the wrong hands.\n\n"
@"If all your passwords are shown and somebody else finds the export, " @"If all your passwords are shown and somebody else finds the export, "
@"they could gain access to all your sites!" @"they could gain access to all your sites!"
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex] + 0) if (buttonIndex == [alert firstOtherButtonIndex] + 0)
// Safe Export // Safe Export
[self exportShowPasswords:NO]; [self exportShowPasswords:NO];
@ -132,12 +127,6 @@
- (void)checkConfig { - (void)checkConfig {
if ([[MPConfig get].saveKey boolValue]) {
if (self.key)
[self updateKey:self.key];
} else
[self loadStoredKey];
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled]) if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES]; [self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
} }
@ -315,7 +304,7 @@
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
[PearlAlert showAlertWithTitle:@"Import Password" message: [PearlAlert showAlertWithTitle:@"Import Password" message:
@"Enter the master password for this export:" @"Enter the master password for this export:"
viewStyle:UIAlertViewStyleSecureTextInput tappedButtonBlock: viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) { ^(UIAlertView *alert, NSInteger buttonIndex) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text
@ -328,6 +317,7 @@
[PearlAlert showAlertWithTitle:@"Import Sites?" [PearlAlert showAlertWithTitle:@"Import Sites?"
message:PearlLocalize(@"Import %d sites, overwriting %d existing sites?", importCount, deleteCount) message:PearlLocalize(@"Import %d sites, overwriting %d existing sites?", importCount, deleteCount)
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex != [alert cancelButtonIndex]) if (buttonIndex != [alert cancelButtonIndex])
confirmation = YES; confirmation = YES;
@ -409,8 +399,8 @@
[self saveContext]; [self saveContext];
if (![[MPiOSConfig get].rememberKey boolValue]) { if (![[MPiOSConfig get].rememberLogin boolValue]) {
[self updateKey:nil]; [self unsetKey];
[self loadKey:NO]; [self loadKey:NO];
} }
@ -455,7 +445,7 @@
message: message:
@"iCloud is now disabled.\n\n" @"iCloud is now disabled.\n\n"
@"It is highly recommended you enable iCloud." @"It is highly recommended you enable iCloud."
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex] + 0) { if (buttonIndex == [alert firstOtherButtonIndex] + 0) {
[PearlAlert showAlertWithTitle:@"About iCloud" [PearlAlert showAlertWithTitle:@"About iCloud"
message: message:
@ -471,6 +461,7 @@
@"with your master password.\n\n" @"with your master password.\n\n"
@"Apple can never see any of your passwords." @"Apple can never see any of your passwords."
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled]; [self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
} }

View File

@ -9,9 +9,8 @@
#import "MPTypeViewController.h" #import "MPTypeViewController.h"
#import "MPElementEntity.h" #import "MPElementEntity.h"
#import "MPSearchDelegate.h" #import "MPSearchDelegate.h"
#import "IASKAppSettingsViewController.h"
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate> @interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate>
@property (strong, nonatomic) MPElementEntity *activeElement; @property (strong, nonatomic) MPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController; @property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;

View File

@ -10,8 +10,7 @@
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPElementGeneratedEntity.h" #import "MPEntities.h"
#import "MPElementStoredEntity.h"
#import "IASKAppSettingsViewController.h" #import "IASKAppSettingsViewController.h"
#import "ATConnect.h" #import "ATConnect.h"
@ -74,7 +73,7 @@
[super viewWillAppear:animated]; [super viewWillAppear:animated];
if (![self.activeElement.keyID isEqualToData:[MPAppDelegate get].keyID]) if (self.activeElement.user != [MPAppDelegate get].activeUser)
self.activeElement = nil; self.activeElement = nil;
self.searchDisplayController.searchBar.text = nil; self.searchDisplayController.searchBar.text = nil;
@ -157,9 +156,9 @@
[self setHelpChapter:self.activeElement? @"2": @"1"]; [self setHelpChapter:self.activeElement? @"2": @"1"];
self.siteName.text = self.activeElement.name; self.siteName.text = self.activeElement.name;
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0; self.passwordCounter.alpha = [self.activeElement.type unsignedIntegerValue] & MPElementTypeClassGenerated? 0.5f: 0;
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0; self.passwordIncrementer.alpha = [self.activeElement.type unsignedIntegerValue] & MPElementTypeClassGenerated? 0.5f: 0;
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0; self.passwordEdit.alpha = [self.activeElement.type unsignedIntegerValue] & MPElementTypeClassStored? 0.5f: 0;
[self.typeButton setTitle:NSStringFromMPElementType((unsigned)self.activeElement.type) [self.typeButton setTitle:NSStringFromMPElementType((unsigned)self.activeElement.type)
forState:UIControlStateNormal]; forState:UIControlStateNormal];
@ -300,7 +299,7 @@
@"You will then need to update your account's old password to this newly generated password.\n\n" @"You will then need to update your account's old password to this newly generated password.\n\n"
@"You can reset the counter by holding down on this button." @"You can reset the counter by holding down on this button."
do:^{ do:^{
++((MPElementGeneratedEntity *) self.activeElement).counter; PearlUnsignedIntegerOp([((MPElementGeneratedEntity *) self.activeElement) counter], +1);
}]; }];
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter]; [TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter];
@ -314,7 +313,7 @@
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
// Not of a type that supports a password counter. // Not of a type that supports a password counter.
return; return;
if (((MPElementGeneratedEntity *)self.activeElement).counter == 1) if ([((MPElementGeneratedEntity *)self.activeElement).counter unsignedIntegerValue] == 1)
// Counter has initial value, no point resetting. // Counter has initial value, no point resetting.
return; return;
@ -323,7 +322,7 @@
@"If you continue, the site's password will change back to its original value. " @"If you continue, the site's password will change back to its original value. "
@"You will then need to update your account's password back to this original value." @"You will then need to update your account's password back to this original value."
do:^{ do:^{
((MPElementGeneratedEntity *) self.activeElement).counter = 1; ((MPElementGeneratedEntity *) self.activeElement).counter = PearlUnsignedInteger(1);
}]; }];
[TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter]; [TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter];
@ -332,6 +331,7 @@
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; { - (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault [PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
@ -361,7 +361,7 @@
- (IBAction)editPassword { - (IBAction)editPassword {
if (self.activeElement.type & MPElementTypeClassStored) { if ([self.activeElement.type unsignedIntegerValue] & MPElementTypeClassStored) {
self.contentField.enabled = YES; self.contentField.enabled = YES;
[self.contentField becomeFirstResponder]; [self.contentField becomeFirstResponder];
} }
@ -403,9 +403,7 @@
break; break;
} }
case 3: { case 3: {
IASKAppSettingsViewController *settingsVC = [IASKAppSettingsViewController new]; [self performSegueWithIdentifier:@"UserProfile" sender:self];
settingsVC.delegate = self;
[self.navigationController pushViewController:settingsVC animated:YES];
break; break;
} }
case 4: { case 4: {
@ -436,7 +434,7 @@
[TestFlight passCheckpoint:MPTestFlightCheckpointAction]; [TestFlight passCheckpoint:MPTestFlightCheckpointAction];
} }
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles: cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings", @"Export", @"Feedback", @"Sign Out", nil]; [self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
} }
- (MPElementType)selectedType { - (MPElementType)selectedType {
@ -458,7 +456,7 @@
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:[MPAppDelegate managedObjectContext]]; inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name; newElement.name = self.activeElement.name;
newElement.keyID = self.activeElement.keyID; newElement.user = self.activeElement.user;
newElement.uses = self.activeElement.uses; newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed; newElement.lastUsed = self.activeElement.lastUsed;
@ -466,7 +464,7 @@
self.activeElement = newElement; self.activeElement = newElement;
}]; }];
self.activeElement.type = type; self.activeElement.type = PearlUnsignedInteger(type);
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]]; [TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
@ -481,7 +479,7 @@
if (element) { if (element) {
self.activeElement = element; self.activeElement = element;
if ([self.activeElement use] == 1) if ([[self.activeElement use] unsignedIntegerValue] == 1)
[self showAlertWithTitle:@"New Site" message: [self showAlertWithTitle:@"New Site" message:
PearlLocalize(@"You've just created a password for %@.\n\n" PearlLocalize(@"You've just created a password for %@.\n\n"
@"IMPORTANT:\n" @"IMPORTANT:\n"
@ -552,10 +550,4 @@
return YES; return YES;
} }
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
while ([self.navigationController.viewControllers containsObject:sender])
[self.navigationController popViewControllerAnimated:YES];
}
@end @end

View File

@ -0,0 +1,16 @@
//
// MPPreferencesViewController.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "IASKAppSettingsViewController.h"
@interface MPPreferencesViewController : UITableViewController <IASKSettingsDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *avatarScrollView;
@end

View File

@ -0,0 +1,63 @@
//
// MPPreferencesViewController.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPPreferencesViewController.h"
#import "MPAppDelegate.h"
@interface MPPreferencesViewController ()
@end
@implementation MPPreferencesViewController
@synthesize avatarScrollView;
- (void)viewDidLoad {
__block NSInteger avatarIndex = 0;
[self.avatarScrollView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
UIButton *avatar = (UIButton *)subview;
avatar.toggleSelectionWhenTouchedInside = YES;
avatar.tag = avatarIndex++;
[avatar onSelect:^(BOOL selected) {
[MPAppDelegate get].activeUser.avatar = PearlInteger(avatar.tag);
[self.avatarScrollView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
UIButton *avatar = (UIButton *)subview;
avatar.selected = ([[MPAppDelegate get].activeUser.avatar integerValue] == avatar.tag);
} recurse:NO];
} options:0];
} recurse:NO];
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[PearlUIUtils autoSizeContent:self.avatarScrollView];
[super viewWillAppear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark -
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
while ([self.navigationController.viewControllers containsObject:sender])
[self.navigationController popViewControllerAnimated:YES];
}
- (void)viewDidUnload {
[self setAvatarScrollView:nil];
[super viewDidUnload];
}
@end

View File

@ -131,8 +131,8 @@
assert(self.query); assert(self.query);
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND keyID == %@", self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
self.query, self.query, NilToNull([MPAppDelegate get].keyID)]; self.query, self.query, NilToNull([MPAppDelegate get].activeUser)];
NSError *error; NSError *error;
if (![self.fetchedResultsController performFetch:&error]) if (![self.fetchedResultsController performFetch:&error])
@ -147,7 +147,7 @@
[self.tipView removeFromSuperview]; [self.tipView removeFromSuperview];
[overlay addSubview:self.tipView]; [overlay addSubview:self.tipView];
} }
return YES; return YES;
} }
@ -280,7 +280,7 @@
cell.textLabel.text = element.name; cell.textLabel.text = element.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"Used %d times, last on %@", cell.detailTextLabel.text = [NSString stringWithFormat:@"Used %d times, last on %@",
element.uses, [self.dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:element.lastUsed]]]; element.uses, [self.dateFormatter stringFromDate:element.lastUsed]];
} else { } else {
// "New" section // "New" section
cell.textLabel.text = self.query; cell.textLabel.text = self.query;
@ -299,6 +299,7 @@
[PearlAlert showAlertWithTitle:@"New Site" [PearlAlert showAlertWithTitle:@"New Site"
message:PearlLocalize(@"Do you want to create a new site named:\n%@", siteName) message:PearlLocalize(@"Do you want to create a new site named:\n%@", siteName)
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
@ -309,10 +310,10 @@
MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class]) MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
inManagedObjectContext:self.fetchedResultsController.managedObjectContext]; inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]); assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]);
assert([MPAppDelegate get].keyID); assert([MPAppDelegate get].activeUser.keyID);
element.name = siteName; element.name = siteName;
element.keyID = [MPAppDelegate get].keyID; element.user = [MPAppDelegate get].activeUser;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didSelectElement:element]; [self.delegate didSelectElement:element];

View File

@ -62,17 +62,15 @@
if ([delegate respondsToSelector:@selector(selectedType)]) if ([delegate respondsToSelector:@selector(selectedType)])
if ([delegate selectedType] == [self typeAtIndexPath:indexPath]) if ([delegate selectedType] == [self typeAtIndexPath:indexPath])
[cell iterateSubviewsContinueAfter:^BOOL(UIView *subview) { [cell enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if ([subview isKindOfClass:[UIImageView class]]) { if ([subview isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = ((UIImageView *)subview); UIImageView *imageView = ((UIImageView *)subview);
if (!imageView.highlightedImage) if (!imageView.highlightedImage)
imageView.highlightedImage = [imageView.image highlightedImage]; imageView.highlightedImage = [imageView.image highlightedImage];
imageView.highlighted = YES; imageView.highlighted = YES;
return NO; *stop = YES;
} }
} recurse:NO];
return YES;
}];
return cell; return cell;
} }

View File

@ -8,14 +8,17 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface MPUnlockViewController : UIViewController <UITextFieldDelegate> @interface MPUnlockViewController : UIViewController <UITextFieldDelegate, UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *lock;
@property (weak, nonatomic) IBOutlet UIImageView *spinner; @property (weak, nonatomic) IBOutlet UIImageView *spinner;
@property (weak, nonatomic) IBOutlet UITextField *field; @property (weak, nonatomic) IBOutlet UITextField *passwordField;
@property (weak, nonatomic) IBOutlet UILabel *messageLabel; @property (weak, nonatomic) IBOutlet UIView *passwordView;
@property (weak, nonatomic) IBOutlet UIView *changeMPView; @property (weak, nonatomic) IBOutlet UIScrollView *usersView;
@property (weak, nonatomic) IBOutlet UILabel *usernameLabel;
@property (weak, nonatomic) IBOutlet UILabel *oldUsernameLabel;
@property (weak, nonatomic) IBOutlet UIButton *userButtonTemplate;
@property (weak, nonatomic) IBOutlet UILabel *deleteTip;
- (IBAction)changeMP; - (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender;
@end @end

View File

@ -12,78 +12,29 @@
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPElementEntity.h" #import "MPEntities.h"
typedef enum {
MPLockscreenIdle,
MPLockscreenError,
MPLockscreenSuccess,
MPLockscreenProgress,
} MPLockscreen;
@interface MPUnlockViewController () @interface MPUnlockViewController ()
@property (strong, nonatomic) MPUserEntity *selectedUser;
@property (strong, nonatomic) NSMutableDictionary *avatarToUser;
@end @end
@implementation MPUnlockViewController @implementation MPUnlockViewController
@synthesize lock; @synthesize selectedUser;
@synthesize avatarToUser;
@synthesize spinner; @synthesize spinner;
@synthesize field; @synthesize passwordField;
@synthesize messageLabel; @synthesize passwordView;
@synthesize changeMPView; @synthesize usersView;
@synthesize usernameLabel, oldUsernameLabel;
@synthesize userButtonTemplate;
@synthesize deleteTip;
- (void)showMessage:(NSString *)message state:(MPLockscreen)state { // [UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
// self.lock.alpha = 0.5f;
__block void(^showMessageAnimation)(void) = ^{ // } completion:nil];
self.lock.alpha = 0.0f;
switch (state) {
case MPLockscreenIdle:
[self.lock setImage:[UIImage imageNamed:@"lock_idle"]];
break;
case MPLockscreenError:
[self.lock setImage:[UIImage imageNamed:@"lock_red"]];
break;
case MPLockscreenSuccess:
[self.lock setImage:[UIImage imageNamed:@"lock_green"]];
break;
case MPLockscreenProgress:
[self.lock setImage:[UIImage imageNamed:@"lock_blue"]];
break;
}
self.lock.alpha = 0.0f;
[UIView animateWithDuration:1.0f animations:^{
self.lock.alpha = 1.0f;
} completion:^(BOOL finished) {
if (finished)
[UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
self.lock.alpha = 0.5f;
} completion:nil];
}];
[UIView animateWithDuration:0.5f animations:^{
self.messageLabel.alpha = 1.0f;
self.messageLabel.text = message;
}];
};
if (self.messageLabel.alpha)
[UIView animateWithDuration:0.3f animations:^{
self.messageLabel.alpha = 0.0f;
} completion:^(BOOL finished) {
if (finished)
showMessageAnimation();
}];
else
showMessageAnimation();
}
- (void)hideMessage {
[UIView animateWithDuration:0.5f animations:^{
self.messageLabel.alpha = 0.0f;
}];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
@ -92,16 +43,16 @@ typedef enum {
- (void)viewDidLoad { - (void)viewDidLoad {
self.messageLabel.text = nil; self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3];
self.messageLabel.alpha = 0;
self.changeMPView.alpha = 0;
self.spinner.alpha = 0;
self.field.text = nil;
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten self.spinner.alpha = 0;
object:nil queue:nil usingBlock:^(NSNotification *note) { self.passwordField.text = nil;
[self.field becomeFirstResponder]; self.usersView.decelerationRate = UIScrollViewDecelerationRateFast;
}]; self.usersView.clipsToBounds = NO;
self.usernameLabel.layer.cornerRadius = 5;
self.userButtonTemplate.hidden = YES;
[self updateLayoutAnimated:NO allowScroll:YES completion:nil];
[super viewDidLoad]; [super viewDidLoad];
} }
@ -109,20 +60,29 @@ typedef enum {
- (void)viewDidUnload { - (void)viewDidUnload {
[self setSpinner:nil]; [self setSpinner:nil];
[self setField:nil]; [self setPasswordField:nil];
[self setPasswordView:nil];
[self setMessageLabel:nil]; [self setUsersView:nil];
[self setLock:nil]; [self setUsernameLabel:nil];
[self setChangeMPView:nil]; [self setUserButtonTemplate:nil];
[self setDeleteTip:nil];
[super viewDidUnload]; [super viewDidUnload];
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
self.selectedUser = nil;
[self updateUsers];
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[[UIApplication sharedApplication] setStatusBarHidden:YES [[UIApplication sharedApplication] setStatusBarHidden:YES
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone]; withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
[super viewWillAppear:animated]; [super viewDidAppear:animated];
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
@ -133,24 +93,247 @@ typedef enum {
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
} }
- (void)viewDidAppear:(BOOL)animated { - (void)updateUsers {
[self.field becomeFirstResponder]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
[super viewDidAppear:animated]; NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil];
// Clean up avatars.
for (UIView *view in [self.usersView subviews])
if (view != self.userButtonTemplate)
[view removeFromSuperview];
[self.avatarToUser removeAllObjects];
// Create avatars.
for (MPUserEntity *user in users)
[self setupAvatar:[PearlUIUtils copyOf:self.userButtonTemplate] forUser:user];
[self setupAvatar:[PearlUIUtils copyOf:self.userButtonTemplate] forUser:nil];
// Scroll view's content changed, update its content size.
[PearlUIUtils autoSizeContent:self.usersView ignoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
self.deleteTip.alpha = 0;
if ([users count] > 1)
[UIView animateWithDuration:0.5f animations:^{
self.deleteTip.alpha = 1;
}];
} }
- (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user {
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
if (highlighted || selected)
avatar.backgroundColor = self.userButtonTemplate.backgroundColor;
else
avatar.backgroundColor = [UIColor clearColor];
} options:0];
[avatar onSelect:^(BOOL selected) {
self.selectedUser = selected? user: nil;
if (user)
[self didToggleUserSelection];
else if (selected)
[self didSelectNewUserAvatar:avatar];
} options:0];
avatar.toggleSelectionWhenTouchedInside = YES;
avatar.center = CGPointMake(avatar.center.x + [self.avatarToUser count] * 160, avatar.center.y);
avatar.hidden = NO;
avatar.layer.cornerRadius = 5;
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
avatar.layer.shadowOpacity = 1;
avatar.layer.shadowRadius = 20;
avatar.layer.masksToBounds = NO;
avatar.backgroundColor = [UIColor clearColor];
if (user)
[self.avatarToUser setObject:user forKey:[NSValue valueWithNonretainedObject:avatar]];
if (self.selectedUser && user == self.selectedUser)
avatar.selected = YES;
return avatar;
}
- (void)didToggleUserSelection {
if (!self.selectedUser)
[self.passwordField resignFirstResponder];
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
if (finished)
if (self.selectedUser)
[self.passwordField becomeFirstResponder];
}];
}
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
[PearlAlert showAlertWithTitle:@"New User"
message:@"Enter your name:" viewStyle:UIAlertViewStylePlainTextInput
initAlert:^(UIAlertView *alert, UITextField *firstField) {
firstField.autocapitalizationType = UITextAutocapitalizationTypeWords;
firstField.autocorrectionType = UITextAutocorrectionTypeYes;
firstField.spellCheckingType = UITextSpellCheckingTypeYes;
firstField.keyboardType = UIKeyboardTypeAlphabet;
}
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
newUserAvatar.selected = NO;
if (buttonIndex == [alert cancelButtonIndex])
return;
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newUser.name = [alert textFieldAtIndex:0].text;
self.selectedUser = newUser;
[self updateUsers];
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
}
- (void)updateLayoutAnimated:(BOOL)animated allowScroll:(BOOL)allowScroll completion:(void (^)(BOOL finished))completion {
if (animated) {
self.oldUsernameLabel.text = self.usernameLabel.text;
self.oldUsernameLabel.alpha = 1;
self.usernameLabel.alpha = 0;
[UIView animateWithDuration:0.5f animations:^{
[self updateLayoutAnimated:NO allowScroll:allowScroll completion:nil];
self.oldUsernameLabel.alpha = 0;
self.usernameLabel.alpha = 1;
} completion:^(BOOL finished) {
if (completion)
completion(finished);
}];
return;
}
if (self.selectedUser && !self.passwordView.alpha) {
self.passwordView.alpha = 1;
self.usersView.center = CGPointMake(160, 100);
self.usersView.scrollEnabled = NO;
self.usernameLabel.center = CGPointMake(160, 84);
self.usernameLabel.backgroundColor = [UIColor blackColor];
self.oldUsernameLabel.center = self.usernameLabel.center;
} else if (self.passwordView.alpha == 1) {
self.passwordView.alpha = 0;
self.usersView.center = CGPointMake(160, 240);
self.usersView.scrollEnabled = YES;
self.usernameLabel.center = CGPointMake(160, 296);
self.usernameLabel.backgroundColor = [UIColor clearColor];
self.oldUsernameLabel.center = self.usernameLabel.center;
}
MPUserEntity *targetedUser = self.selectedUser;
UIButton *selectedAvatar = [self avatarForUser:self.selectedUser];
UIButton *targetedAvatar = selectedAvatar;
if (!targetedAvatar) {
targetedAvatar = [self findTargetedAvatar];
targetedUser = [self userForAvatar:targetedAvatar];
}
[self.usersView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
const BOOL isTargeted = subview == targetedAvatar;
subview.userInteractionEnabled = isTargeted;
subview.alpha = isTargeted ? 1: self.selectedUser? 0.1: 0.4;
if (!isTargeted && [subview.layer animationForKey:@"targetedShadow"]) {
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
toShadowColorAnimation.toValue = (__bridge id)[UIColor blackColor].CGColor;
toShadowColorAnimation.duration = 0.5f;
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
toShadowOpacityAnimation.toValue = PearlFloat(1);
toShadowOpacityAnimation.duration = 0.5f;
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, nil];
group.duration = 0.5f;
[subview.layer removeAnimationForKey:@"targetedShadow"];
[subview.layer addAnimation:group forKey:@"inactiveShadow"];
}
} recurse:NO];
if (![targetedAvatar.layer animationForKey:@"targetedShadow"]) {
CABasicAnimation *toShadowColorAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"];
toShadowColorAnimation.toValue = (__bridge id)[UIColor whiteColor].CGColor;
toShadowColorAnimation.beginTime = 0.0f;
toShadowColorAnimation.duration = 0.5f;
toShadowColorAnimation.fillMode = kCAFillModeForwards;
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
toShadowOpacityAnimation.toValue = PearlFloat(0.2);
toShadowOpacityAnimation.duration = 0.5f;
CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
pulseShadowOpacityAnimation.fromValue = PearlFloat(0.2);
pulseShadowOpacityAnimation.toValue = PearlFloat(0.6);
pulseShadowOpacityAnimation.beginTime = 0.5f;
pulseShadowOpacityAnimation.duration = 2.0f;
pulseShadowOpacityAnimation.autoreverses = YES;
pulseShadowOpacityAnimation.repeatCount = NSIntegerMax;
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation, nil];
group.duration = CGFLOAT_MAX;
[targetedAvatar.layer removeAnimationForKey:@"inactiveShadow"];
[targetedAvatar.layer addAnimation:group forKey:@"targetedShadow"];
}
if (allowScroll) {
CGPoint targetContentOffset = CGPointMake(targetedAvatar.center.x - self.usersView.bounds.size.width / 2, self.usersView.contentOffset.y);
if (!CGPointEqualToPoint(self.usersView.contentOffset, targetContentOffset))
[self.usersView setContentOffset:targetContentOffset animated:animated];
}
self.usernameLabel.text = targetedUser? targetedUser.name: @"New User";
self.usernameLabel.bounds = CGRectSetHeight(self.usernameLabel.bounds,
[self.usernameLabel.text sizeWithFont:self.usernameLabel.font
constrainedToSize:CGSizeMake(self.usernameLabel.bounds.size.width - 10, 100)
lineBreakMode:self.usernameLabel.lineBreakMode].height);
self.oldUsernameLabel.bounds = self.usernameLabel.bounds;
if (completion)
completion(YES);
}
- (UIButton *)findTargetedAvatar {
CGFloat xOfMiddle = self.usersView.contentOffset.x + self.usersView.bounds.size.width / 2;
return (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, self.usersView.contentOffset.y) ofArray:self.usersView.subviews];
}
- (UIButton *)avatarForUser:(MPUserEntity *)user {
__block UIButton *avatar = nil;
if (user)
[self.avatarToUser enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (obj == user)
avatar = [key nonretainedObjectValue];
}];
return avatar;
}
- (MPUserEntity *)userForAvatar:(UIButton *)avatar {
return NullToNil([self.avatarToUser objectForKey:[NSValue valueWithNonretainedObject:avatar]]);
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField { - (BOOL)textFieldShouldReturn:(UITextField *)textField {
if ([textField.text length]) { if (![textField.text length])
[textField resignFirstResponder]; return NO;
return YES;
}
return NO; [textField resignFirstResponder];
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
@ -166,52 +349,53 @@ typedef enum {
self.spinner.alpha = 1.0f; self.spinner.alpha = 1.0f;
}]; }];
[self showMessage:@"Checking password..." state:MPLockscreenProgress]; // [self showMessage:@"Checking password..." state:MPLockscreenProgress];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL unlocked = [[MPAppDelegate get] tryMasterPassword:textField.text]; BOOL unlocked = [[MPAppDelegate get] tryMasterPassword:textField.text forUser:self.selectedUser];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (unlocked) { if (unlocked) {
[self showMessage:@"Success!" state:MPLockscreenSuccess]; // [self showMessage:@"Success!" state:MPLockscreenSuccess];
if ([selectedUser.keyID length])
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", [MPAppDelegate get].keyID];
fetchRequest.fetchLimit = 1;
BOOL keyIDHasElements = [[[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil] count] > 0;
if (keyIDHasElements)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES]; [self dismissModalViewControllerAnimated:YES];
}); });
else { else {
[PearlAlert showAlertWithTitle:@"New Master Password" [PearlAlert showAlertWithTitle:@"New Master Password"
message: message:@"Please confirm the spelling of this new master password."
@"Please confirm the spelling of this new master password."
viewStyle:UIAlertViewStyleSecureTextInput viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) { if (buttonIndex == [alert cancelButtonIndex]) {
[[MPAppDelegate get] updateKey:nil]; [[MPAppDelegate get] unsetKey];
return; return;
} }
if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) { if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) {
[PearlAlert showAlertWithTitle:@"Incorrect Master Password" [PearlAlert showAlertWithTitle:@"Incorrect Master Password"
message: message:
@"The password you entered doesn't match with the master password you tried to use. " @"The password you entered doesn't match with the master password you tried to use. "
@"You've probably mistyped one of them.\n\n" @"You've probably mistyped one of them.\n\n"
@"Give it another try." @"Give it another try."
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil]; cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
return; return;
} }
self.selectedUser.keyID = [MPAppDelegate get].activeUser.keyID;
[[MPAppDelegate get] saveContext];
[self dismissModalViewControllerAnimated:YES]; [self dismissModalViewControllerAnimated:YES];
} }
cancelTitle:[PearlStrings get].commonButtonCancel cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:[PearlStrings get].commonButtonContinue, nil]; otherTitles:[PearlStrings get].commonButtonContinue, nil];
} }
} else { } else {
[self showMessage:@"Not valid." state:MPLockscreenError]; // [self showMessage:@"Not valid." state:MPLockscreenError];
[UIView animateWithDuration:0.5f animations:^{ [UIView animateWithDuration:0.5f animations:^{
self.changeMPView.alpha = 1.0f; // self.changeMPView.alpha = 1.0f;
}]; }];
} }
@ -224,8 +408,39 @@ typedef enum {
}); });
}); });
}); });
return YES;
} }
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGFloat xOfMiddle = targetContentOffset->x + scrollView.bounds.size.width / 2;
UIButton *middleAvatar = (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, targetContentOffset->y) ofArray:scrollView.subviews];
*targetContentOffset = CGPointMake(middleAvatar.center.x - scrollView.bounds.size.width / 2, targetContentOffset->y);
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
// [self scrollToAvatar:middleAvatar animated:YES];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
// [self scrollToAvatar:middleAvatar animated:YES];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// CGFloat xOfMiddle = scrollView.contentOffset.x + scrollView.bounds.size.width / 2;
// UIButton *middleAvatar = (UIButton *)[PearlUIUtils viewClosestTo:CGPointMake(xOfMiddle, scrollView.contentOffset.y) ofArray:scrollView.subviews];
//
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
// [self scrollToAvatar:middleAvatar animated:NO];
}
#pragma mark - IBActions
- (IBAction)changeMP { - (IBAction)changeMP {
[PearlAlert showAlertWithTitle:@"Changing Master Password" [PearlAlert showAlertWithTitle:@"Changing Master Password"
@ -236,11 +451,12 @@ typedef enum {
@"You can always change back to your current master password later.\n" @"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again." @"Your current sites and passwords will then become available again."
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
[[MPAppDelegate get] forgetKey]; [[MPAppDelegate get] forgetSavedKey];
[[MPAppDelegate get] loadKey:YES]; [[MPAppDelegate get] loadKey:YES];
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged]; [TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
@ -249,4 +465,30 @@ typedef enum {
otherTitles:[PearlStrings get].commonButtonContinue, nil]; otherTitles:[PearlStrings get].commonButtonContinue, nil];
} }
- (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan)
return;
if (self.selectedUser)
return;
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]];
if (!targetedUser)
return;
[PearlAlert showAlertWithTitle:@"Delete User" message:
PearlString(@"Do you want to delete all record of the following user?\n\n%@", targetedUser.name)
viewStyle:UIAlertViewStyleDefault
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
[[MPAppDelegate get] saveContext];
[self updateUsers];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Delete", nil];
}
@end @end

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2182" systemVersion="11E53" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2182" systemVersion="11E53" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
<dependencies> <dependencies>
<deployment defaultVersion="1296" identifier="iOS"/>
<development defaultVersion="4200" identifier="xcode"/> <development defaultVersion="4200" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1181"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1181"/>
</dependencies> </dependencies>
@ -659,6 +658,7 @@ L4m3P4sSw0rD</string>
<outlet property="siteName" destination="gSK-aB-wNI" id="IIe-z8-zy8"/> <outlet property="siteName" destination="gSK-aB-wNI" id="IIe-z8-zy8"/>
<outlet property="typeButton" destination="Cei-5z-uWE" id="4M1-d7-5Bh"/> <outlet property="typeButton" destination="Cei-5z-uWE" id="4M1-d7-5Bh"/>
<outlet property="typeTipContainer" destination="g55-0m-WjS" id="KZ9-KV-NMh"/> <outlet property="typeTipContainer" destination="g55-0m-WjS" id="KZ9-KV-NMh"/>
<segue destination="oLN-6u-GLb" kind="push" identifier="UserProfile" id="tKN-Sw-S5J"/>
</connections> </connections>
</viewController> </viewController>
<pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="cZr-Fj-eBw"> <pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="cZr-Fj-eBw">
@ -735,7 +735,7 @@ L4m3P4sSw0rD</string>
<scene sceneID="HkH-JR-Fhy"> <scene sceneID="HkH-JR-Fhy">
<objects> <objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="OGA-5j-IcQ" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="OGA-5j-IcQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController storyboardIdentifier="MPUnlockViewController" modalTransitionStyle="flipHorizontal" id="Nbn-Rv-sP1" customClass="MPUnlockViewController" sceneMemberID="viewController"> <viewController storyboardIdentifier="MPUnlockViewController" wantsFullScreenLayout="YES" modalTransitionStyle="flipHorizontal" id="Nbn-Rv-sP1" customClass="MPUnlockViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="PHH-XC-9QQ"> <view key="view" contentMode="scaleToFill" id="PHH-XC-9QQ">
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/> <rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
@ -744,96 +744,111 @@ L4m3P4sSw0rD</string>
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/> <rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView> </imageView>
<view contentMode="scaleToFill" id="OP6-72-eij"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.5" contentMode="left" text="Tap and hold to delete." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="DBJ-Qi-ZcF">
<rect key="frame" x="0.0" y="157" width="320" height="130"/> <rect key="frame" x="32" y="460" width="256" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="12"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</label>
<view contentMode="scaleToFill" id="7cc-yu-i0m">
<rect key="frame" x="20" y="168" width="280" height="88"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="lock_idle.png" id="tyv-qJ-bKR"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
<rect key="frame" x="110" y="15" width="100" height="100"/> <rect key="frame" x="12" y="0.0" width="256" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</imageView> <fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
<imageView userInteractionEnabled="NO" alpha="0.0" contentMode="center" image="lock_idle.png" id="Lpf-KA-3CV">
<rect key="frame" x="110" y="15" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_spinner.png" id="27q-lX-0vy">
<rect key="frame" x="122" y="28" width="75" height="75"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Checking password..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="oU9-lf-nnJ">
<rect key="frame" x="20" y="115" width="280" height="15"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT">
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
</imageView>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o">
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="36"/>
<textInputTraits key="textInputTraits" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
</connections>
</textField>
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_spinner.png" id="27q-lX-0vy">
<rect key="frame" x="20" y="89" width="280" height="60"/> <rect key="frame" x="105" y="29" width="110" height="110"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
</imageView> </imageView>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o"> <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" delaysContentTouches="NO" id="Blg-F1-9NA">
<rect key="frame" x="20" y="89" width="280" height="60"/> <rect key="frame" x="0.0" y="20" width="320" height="160"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="36"/>
<textInputTraits key="textInputTraits" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
</connections>
</textField>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
<rect key="frame" x="32" y="61" width="256" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" id="Wu7-Mg-9SD">
<rect key="frame" x="0.0" y="391" width="320" height="89"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Trying to log in with another master password?" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="vnS-n6-NZI"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Ten-ig-gog">
<rect key="frame" x="0.0" y="0.0" width="320" height="15"/> <rect key="frame" x="105" y="10" width="110" height="110"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="11"/> <color key="backgroundColor" red="0.40000000596046448" green="0.80000001192092896" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="wad-V1-K3f">
<rect key="frame" x="10" y="23" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<size key="titleShadowOffset" width="0.0" height="1"/> <state key="normal" backgroundImage="avatar-male-1.png">
<state key="normal" title="Change master password" backgroundImage="ui_button_standard_large.png"> <color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/> <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</state> </state>
<state key="highlighted"> <state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state> </state>
<connections>
<action selector="changeMP" destination="Nbn-Rv-sP1" eventType="touchUpInside" id="7RI-hu-SiS"/>
</connections>
</button> </button>
</subviews> </subviews>
<gestureRecognizers/>
<connections>
<outlet property="delegate" destination="Nbn-Rv-sP1" id="E1h-WA-PYV"/>
<outletCollection property="gestureRecognizers" destination="9WS-yS-aqQ" appends="YES" id="B9k-bg-gqA"/>
</connections>
</scrollView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.0" contentMode="left" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" id="8s0-nT-Aoq">
<rect key="frame" x="90" y="289" width="140" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> <fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" id="0NM-NI-7UR">
<rect key="frame" x="90" y="76.5" width="140" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="keypad.png" id="4tz-l4-4Kj">
<rect key="frame" x="0.0" y="264" width="320" height="216"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
</subviews> </subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view> </view>
<nil key="simulatedStatusBarMetrics"/> <nil key="simulatedStatusBarMetrics"/>
<connections> <connections>
<outlet property="changeMPView" destination="Wu7-Mg-9SD" id="84H-HT-5ml"/> <outlet property="deleteTip" destination="DBJ-Qi-ZcF" id="VXD-Zc-UYi"/>
<outlet property="field" destination="rTR-7Q-X8o" id="DHg-gg-MVD"/> <outlet property="oldUsernameLabel" destination="8s0-nT-Aoq" id="mXR-VG-2js"/>
<outlet property="lock" destination="Lpf-KA-3CV" id="6cE-2g-4XQ"/> <outlet property="passwordField" destination="rTR-7Q-X8o" id="CDA-iP-kCm"/>
<outlet property="messageLabel" destination="oU9-lf-nnJ" id="Ahc-hl-KnJ"/> <outlet property="passwordView" destination="7cc-yu-i0m" id="WoF-Ab-PPC"/>
<outlet property="spinner" destination="27q-lX-0vy" id="jUx-GK-Lgf"/> <outlet property="spinner" destination="27q-lX-0vy" id="jUx-GK-Lgf"/>
<outlet property="userButtonTemplate" destination="Ten-ig-gog" id="hSM-hh-ofy"/>
<outlet property="usernameLabel" destination="0NM-NI-7UR" id="Sa4-pY-87O"/>
<outlet property="usersView" destination="Blg-F1-9NA" id="SsC-1H-fIB"/>
</connections> </connections>
</viewController> </viewController>
<pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="9WS-yS-aqQ">
<connections>
<action selector="deleteTargetedUser:" destination="Nbn-Rv-sP1" id="cBQ-oQ-c7g"/>
</connections>
</pongPressGestureRecognizer>
</objects> </objects>
<point key="canvasLocation" x="455" y="1425"/> <point key="canvasLocation" x="455" y="1425"/>
</scene> </scene>
@ -857,9 +872,319 @@ L4m3P4sSw0rD</string>
</objects> </objects>
<point key="canvasLocation" x="-85" y="182"/> <point key="canvasLocation" x="-85" y="182"/>
</scene> </scene>
<!--App Settings View Controller-->
<scene sceneID="4E1-FA-zP1">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="Xkt-gh-nAq" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController id="Ypi-Ct-X6S" customClass="IASKAppSettingsViewController" sceneMemberID="viewController">
<navigationItem key="navigationItem" id="u18-ao-KIy"/>
</viewController>
</objects>
<point key="canvasLocation" x="1537" y="785"/>
</scene>
<!--Preferences View Controller - User Profile-->
<scene sceneID="cMu-Lq-D10">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEl-jQ-l9k" userLabel="First Responder" sceneMemberID="firstResponder"/>
<tableViewController id="oLN-6u-GLb" customClass="MPPreferencesViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="oSh-Ap-kLt">
<rect key="frame" x="0.0" y="64" width="320" height="416"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.12549020350000001" green="0.1411764771" blue="0.14901961389999999" alpha="1" colorSpace="calibratedRGB"/>
<view key="tableFooterView" contentMode="scaleToFill" id="63M-7L-M7o">
<rect key="frame" x="0.0" y="508" width="320" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<sections>
<tableViewSection id="OJv-8V-W2l">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="226" id="9gC-Vq-Ta8">
<rect key="frame" x="0.0" y="0.0" width="320" height="226"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="225"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Avatar" lineBreakMode="tailTruncation" minimumFontSize="10" id="0Wd-wn-t6z">
<rect key="frame" x="20" y="20" width="280" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Bold" family="Gill Sans" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="This is the icon that represents you on the Master Password unlock screen." lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" id="DgX-yc-sKT">
<rect key="frame" x="20" y="49" width="280" height="38"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" id="TMH-Xt-EnD">
<rect key="frame" x="0.0" y="95" width="320" height="130"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" id="jEL-UA-da6">
<rect key="frame" x="0.0" y="0.0" width="614" height="130"/>
<autoresizingMask key="autoresizingMask" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" lineBreakMode="middleTruncation" id="6ap-Xw-Ubd">
<rect key="frame" x="20" y="10" width="110" height="110"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="avatar-male-1.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" lineBreakMode="middleTruncation" id="DwC-Fa-yaI">
<rect key="frame" x="138" y="10" width="110" height="110"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="avatar-female-1.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" lineBreakMode="middleTruncation" id="TF1-Zi-jZK">
<rect key="frame" x="256" y="10" width="110" height="110"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="avatar-male-2.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" lineBreakMode="middleTruncation" id="32d-Yo-SGt">
<rect key="frame" x="366" y="10" width="110" height="110"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="avatar-female-2.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" lineBreakMode="middleTruncation" id="LTD-gI-8bH">
<rect key="frame" x="484" y="10" width="110" height="110"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="avatar-male-3.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</subviews>
</scrollView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="TRa-Gy-tG5">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="119" id="n1H-sG-HDc">
<rect key="frame" x="0.0" y="226" width="320" height="119"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="118"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Preferences" lineBreakMode="tailTruncation" minimumFontSize="10" id="ylz-i5-tem">
<rect key="frame" x="20" y="20" width="280" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Bold" family="Gill Sans" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="These are user-specific configuration settings." lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" id="WCm-A3-a1a">
<rect key="frame" x="20" y="49" width="280" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="top" image="ui_list_first.png" id="tK4-bL-QK1">
<rect key="frame" x="10" y="75" width="300" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="ui_list_last.png" id="fAY-fK-Uzn">
<rect key="frame" x="10" y="109" width="300" height="10"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Save Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="mmP-r2-iNF">
<rect key="frame" x="20" y="75" width="280" height="23"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Stores your password on the device." lineBreakMode="tailTruncation" minimumFontSize="10" id="LRv-ac-sdH">
<rect key="frame" x="20" y="98" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" id="seh-qE-k6P">
<rect key="frame" x="221" y="83" width="79" height="27"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="onTintColor" red="0.12549020350000001" green="0.1411764771" blue="0.14901961389999999" alpha="1" colorSpace="calibratedRGB"/>
</switch>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="dN3-cJ-9WA">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="119" id="xBt-OT-BYA">
<rect key="frame" x="0.0" y="345" width="320" height="119"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="118"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Operations" lineBreakMode="tailTruncation" minimumFontSize="10" id="VI9-uT-nrJ">
<rect key="frame" x="20" y="20" width="280" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="GillSans-Bold" family="Gill Sans" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Advanced operations for managing your passwords." lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" id="blC-Mc-59X">
<rect key="frame" x="20" y="49" width="280" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_first.png" id="zeK-qR-5wK">
<rect key="frame" x="10" y="75" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Export" lineBreakMode="tailTruncation" minimumFontSize="10" id="DTP-qu-VqT">
<rect key="frame" x="20" y="75" width="280" height="23"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Save all your sites to a file." lineBreakMode="tailTruncation" minimumFontSize="10" id="oi9-2Y-2KM">
<rect key="frame" x="20" y="98" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="glr-eJ-qKq">
<rect key="frame" x="0.0" y="464" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_last.png" id="rbd-L8-fSm">
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Change Master Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="M38-26-Zzv">
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Change your user's login master password." lineBreakMode="tailTruncation" minimumFontSize="10" id="tUk-yd-cyq">
<rect key="frame" x="20" y="23" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
<connections>
<outlet property="dataSource" destination="oLN-6u-GLb" id="a6n-fI-h3l"/>
<outlet property="delegate" destination="oLN-6u-GLb" id="MM9-S2-8rf"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="User Profile" id="reg-hh-9Ra">
<barButtonItem key="rightBarButtonItem" title="Settings" id="Bac-IA-e0Z">
<connections>
<segue destination="Ypi-Ct-X6S" kind="push" id="vLK-dS-gos"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="avatarScrollView" destination="TMH-Xt-EnD" id="zdf-xt-piZ"/>
</connections>
</tableViewController>
</objects>
<point key="canvasLocation" x="996" y="785"/>
</scene>
</scenes> </scenes>
<resources> <resources>
<image name="Square-bottom.png" width="551" height="58"/> <image name="Square-bottom.png" width="551" height="58"/>
<image name="avatar-female-1.png" width="110" height="110"/>
<image name="avatar-female-2.png" width="110" height="110"/>
<image name="avatar-male-1.png" width="110" height="110"/>
<image name="avatar-male-2.png" width="110" height="110"/>
<image name="avatar-male-3.png" width="110" height="110"/>
<image name="background.png" width="480" height="480"/> <image name="background.png" width="480" height="480"/>
<image name="guide_page_0.png" width="320" height="480"/> <image name="guide_page_0.png" width="320" height="480"/>
<image name="guide_page_1.png" width="320" height="480"/> <image name="guide_page_1.png" width="320" height="480"/>
@ -872,7 +1197,7 @@ L4m3P4sSw0rD</string>
<image name="icon_cancel.png" width="32" height="32"/> <image name="icon_cancel.png" width="32" height="32"/>
<image name="icon_edit.png" width="32" height="32"/> <image name="icon_edit.png" width="32" height="32"/>
<image name="icon_plus.png" width="32" height="32"/> <image name="icon_plus.png" width="32" height="32"/>
<image name="lock_idle.png" width="100" height="100"/> <image name="keypad.png" width="320" height="216"/>
<image name="tip_alert_black.png" width="235" height="81"/> <image name="tip_alert_black.png" width="235" height="81"/>
<image name="tip_basic_black.png" width="210" height="60"/> <image name="tip_basic_black.png" width="210" height="60"/>
<image name="tip_basic_black_top.png" width="210" height="60"/> <image name="tip_basic_black_top.png" width="210" height="60"/>
@ -887,6 +1212,86 @@ L4m3P4sSw0rD</string>
<image name="ui_spinner.png" width="75" height="75"/> <image name="ui_spinner.png" width="75" height="75"/>
<image name="ui_textfield.png" width="158" height="34"/> <image name="ui_textfield.png" width="158" height="34"/>
</resources> </resources>
<classes>
<class className="IASKAppSettingsViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/IASKAppSettingsViewController.h"/>
<relationships>
<relationship kind="action" name="dismiss:"/>
<relationship kind="outlet" name="delegate"/>
</relationships>
</class>
<class className="MPGuideViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPGuideViewController.h"/>
<relationships>
<relationship kind="action" name="close"/>
<relationship kind="outlet" name="scrollView" candidateClass="UIScrollView"/>
</relationships>
</class>
<class className="MPMainViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPMainViewController.h"/>
<relationships>
<relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="closeAlert"/>
<relationship kind="action" name="copyContent"/>
<relationship kind="action" name="editPassword"/>
<relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="actionsTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertBody" candidateClass="UITextView"/>
<relationship kind="outlet" name="alertContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertTitle" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentField" candidateClass="UITextField"/>
<relationship kind="outlet" name="contentTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentTipEditIcon" candidateClass="UIImageView"/>
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
<relationship kind="outlet" name="searchResultsController" candidateClass="MPSearchDelegate"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="siteName" candidateClass="UILabel"/>
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="typeTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPPreferencesViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPPreferencesViewController.h"/>
<relationships>
<relationship kind="outlet" name="avatarScrollView" candidateClass="UIScrollView"/>
</relationships>
</class>
<class className="MPSearchDelegate" superclassName="NSObject">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPSearchDelegate.h"/>
<relationships>
<relationship kind="outlet" name="delegate"/>
<relationship kind="outlet" name="searchDisplayController" candidateClass="UISearchDisplayController"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPTypeViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPTypeViewController.h"/>
<relationships>
<relationship kind="outlet" name="recommendedTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPUnlockViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPUnlockViewController.h"/>
<relationships>
<relationship kind="action" name="deleteTargetedUser:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="deleteTip" candidateClass="UILabel"/>
<relationship kind="outlet" name="oldUsernameLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordField" candidateClass="UITextField"/>
<relationship kind="outlet" name="passwordView" candidateClass="UIView"/>
<relationship kind="outlet" name="spinner" candidateClass="UIImageView"/>
<relationship kind="outlet" name="userButtonTemplate" candidateClass="UIButton"/>
<relationship kind="outlet" name="usernameLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="usersView" candidateClass="UIScrollView"/>
</relationships>
</class>
</classes>
<simulatedMetricsContainer key="defaultSimulatedMetrics"> <simulatedMetricsContainer key="defaultSimulatedMetrics">
<nil key="statusBar"/> <nil key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/> <simulatedOrientationMetrics key="orientation"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
Resources/keypad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -31,7 +31,14 @@
var asset_host = is_ssl ? "https://d3rdqalhjaisuu.cloudfront.net/" : "http://d3rdqalhjaisuu.cloudfront.net/"; var asset_host = is_ssl ? "https://d3rdqalhjaisuu.cloudfront.net/" : "http://d3rdqalhjaisuu.cloudfront.net/";
document.write(unescape("%3Cscript src='" + asset_host + "javascripts/feedback-v2.js' type='text/javascript'%3E%3C/script%3E")); document.write(unescape("%3Cscript src='" + asset_host + "javascripts/feedback-v2.js' type='text/javascript'%3E%3C/script%3E"));
</script> </script>
<!-- Get Satisfaction -->
<!--script type="text/javascript" charset="utf-8"> <!--script type="text/javascript" charset="utf-8">
var is_ssl = ("https:" == document.location.protocol);
var asset_host = is_ssl ? "https://d3rdqalhjaisuu.cloudfront.net/" : "http://d3rdqalhjaisuu.cloudfront.net/";
document.write(unescape("%3Cscript src='" + asset_host + "javascripts/feedback-v2.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript" charset="utf-8">
var feedback_widget_options = {}; var feedback_widget_options = {};
feedback_widget_options.display = "overlay"; feedback_widget_options.display = "overlay";
feedback_widget_options.company = "lyndir"; feedback_widget_options.company = "lyndir";
@ -40,6 +47,29 @@
feedback_widget_options.style = "question"; feedback_widget_options.style = "question";
var feedback_widget = new GSFN.feedback_widget(feedback_widget_options); var feedback_widget = new GSFN.feedback_widget(feedback_widget_options);
</script--> </script-->
<!-- UserEcho -->
<script type='text/javascript'>
var _ues = {
host:'support.lyndir.com',
forum:'13031',
lang:'en',
tab_icon_show:false,
tab_corner_radius:5,
tab_font_size:20,
tab_image_hash:'RmVlZGJhY2s%3D',
tab_alignment:'right',
tab_text_color:'#FFFFFF',
tab_bg_color:'#DDDDDD',
tab_hover_color:'#CCCCCC'
};
(function() {
var _ue = document.createElement('script'); _ue.type = 'text/javascript'; _ue.async = true;
_ue.src = ('https:' == document.location.protocol ? 'https://s3.amazonaws.com/' : 'http://') + 'cdn.userecho.com/js/widget-1.4.gz.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(_ue, s);
})();
</script>
</head> </head>
<body> <body>
<header> <header>
@ -66,49 +96,193 @@
The result is that each master password generates its own unique sequence of passwords for any site name. Since the only input data is the master password and the site name (along with a password counter, see below), there is no need for any kind of storage to recreate a site's password. All that's needed is the correct master password and the correct algorithm implementation. What that does for you is make it almost impossible to lose your passwords. It also makes it nearly impossible for hackers to steal your online identity. The result is that each master password generates its own unique sequence of passwords for any site name. Since the only input data is the master password and the site name (along with a password counter, see below), there is no need for any kind of storage to recreate a site's password. All that's needed is the correct master password and the correct algorithm implementation. What that does for you is make it almost impossible to lose your passwords. It also makes it nearly impossible for hackers to steal your online identity.
</p> </p>
<h1>The algorithm</h1> <h1>The Algorithm</h1>
<p> <p>
The user chooses a single master password, preferably sufficiently long to harden against brute-force attacks. The application then creates a <a href="http://www.tarsnap.com/scrypt.html" onclick="_gaq.push(['_trackPageview', '/outbound/tarsnap.com/scrypt.html">scrypt</a> key derivative from the user's password. This process takes quite a bit of processing time and memory. It makes brute-forcing the master password <b>far more difficult</b>, to practically infeasible, even for otherwise vulnerable password strings. Master Password uses a stateless algorithm that relies solely on its implementation and the user's inputs. The user is expected to remember the following information:
<ul>
<li><b>The master password</b> (eg. <em>pink fluffy door frame</em>):<br />
This is a secret that the user shares with nobody.</li>
<li><b>The site name</b> (eg. <em>apple.com</em>):<br />
The user chooses a name for each site. Its domain name is an ideal choice, since it needn't necessarily be remembered.</li>
<li><b>The site's password counter</b> (default: <em>0</em>):<br />
This is an integer that can be incremented when the user needs a new password for the site.</li>
<li><b>The site's password type</b> (default: <em>Long Password</em>):<br />
This type determines the format of the output password. It can be changed if the site's password policy does not accept passwords of this format.</li>
</ul>
</p>
<p>
In short, the algorithm is comprised of the following steps:
<ul>
<li>Determining the master <code>key</code></li>
<li>Determining the cipher <code>seed</code></li>
<li>Encoding a user-friendly <code>password</code></li>
</ul>
</p>
<h2>The Master Password</h2>
<p>
The user chooses a single master password, preferably sufficiently long to harden against brute-force attacks. Master Password recommends absurd two or three-word sentences as they're easily remembered and generally sufficiently high in entropy.
</p>
<p>
The application then creates a <a href="http://www.tarsnap.com/scrypt.html" onclick="_gaq.push(['_trackPageview', '/outbound/tarsnap.com/scrypt.html">scrypt</a> key derivative from the user's password. This process takes quite a bit of processing time and memory. This step exists to make brute-force attempts at guessing the master password from a given output password <b>far more difficult</b>, to practically infeasible, even for otherwise vulnerable password strings.
</p> </p>
<code><pre> <code><pre>
key = scrypt( P, S, N, r, p, dkLen ) key = scrypt( P, S, N, r, p, dkLen )
where where
P = master password P = master password (UTF-8)
S = &lt;empty&gt; S = &lt;empty&gt;
N = 16384 N = 16384
r = 8 r = 8
p = 1 p = 1
dkLen = 64 dkLen = 64
</pre></code> </pre></code>
<p> <p>
When the user requests a password be generated for a site, the application composes a byte string consisting of the <code>site name</code> (UTF-8 decoded), the <code>key</code>, and a <code>salt</code> (a 32-bit unsigned integer in network byte order. Normally this is the password counter), delimited in that order by a single <code>NUL byte</code>, and hashes it using the <code>SHA-1</code> algorithm. The result is called the <code>seed</code>. The result is a 64-byte <code>key</code> derived from the user's master password. This key will be fed into the rest of the algorithm to produce output passwords that are as private to the user as his master password is.
</p>
<h2>Combining The Inputs</h2>
<p>
The theory behind Master Password requires that all inputs are given by the user. The two main inputs are the master password that we used to determine the <code>key</code> and the site's name. There is a third input value, the password counter, which is a 32-bit unsigned integer value that is used to salt the input. Initially, the password counter should be zero, but a user may specify a non-zero counter value in case he wants to force the algorithm to produce a new output password for the site.
</p>
<p>
These input values are combined in a byte array, separated by a single <code>NUL</code> byte. In order, the input values are the <code>site name</code> (UTF-8 decoded), the master <code>key</code>, and a <code>salt</code> (this is the password counter, a 32-bit unsigned integer in network byte order). The byte array is hashed using the SHA-1 algorithm to yield the <code>seed</code> as a result.
</p> </p>
<code><pre> <code><pre>
salt = htonl( password counter ) salt = htonl( password counter )
seed = sha1( site name . "\0" . key . "\0" . salt ) seed = sha1( site name . "\0" . key . "\0" . salt )
</pre></code> </pre></code>
<h2>Generating The Output</h2>
<p> <p>
The seed is now combined with the password type the user has chosen for the site. Password types determine the <q>cipher</q> that will be used to encode the <code>seed</code> bytes into a readable password. For instance, the standard password type <q>Long Password</q> activates one of three pre-set ciphers: <code>CvcvCvcvnoCvcv</code>, <code>CvcvnoCvcvCvcv</code> or <code>CvcvCvcvCvcvno</code>. Which of those will be used, depends on the first byte of the <code>seed</code>. Take the byte value modulo the amount of pre-set ciphers (in this case, three), and the result tells you which of the pre-set ciphers to use. We now have a <code>seed</code> which is a sufficiently long seemingly-arbitrary string of bytes that is unique to the site and the user. This string of bytes, however, is not very useful for a user to use as a password. We have two additional problems that need to be solved: The output password must be easy for a user to read and copy, but it should also be compatible with most password policies.
</p>
<p>
Password policies are strict rules imposed by applications on their users, designed to limit the types of passwords these users are allowed to use with the application. Usually, these policies exist to force users into thinking about passwords with a healthy entropy. Often, they exist purely as a side-effect of bad password handling such as storing the clear-text passwords in a database.
</p>
<p>
Since the idea is that the output password can be used directly as a password to protect the user's account on the site, it needs to be able to pass the site's password policy.
Master Password addresses this problem by introducing <em>password types</em>. Each password type describes what an output password must look like and maps to a set of <code>ciphers</code>. Ciphers describe the resulting output password using a series of characters that map to character groups of candidate output characters. A cipher has the same length as the output password it yields. Each character in the cipher maps to a specific character group. At each position of the output password, a character is chosen from the character group identified by the character in the cipher at the same position.
</p>
<p>
The following ciphers are defined:
<ul>
<li>Type: <b>Long Password</b></li>
<li>
<ul>
<li><code>CvcvCvcvnoCvcv</code></li>
<li><code>CvcvnoCvcvCvcv</code></li>
<li><code>CvcvCvcvCvcvno</code></li>
</ul>
</li>
<li>Type: <b>Medium Password</b></li>
<li>
<ul>
<li><code>CvcnoCvc</code></li>
<li><code>CvcCvcno</code></li>
</ul>
</li>
<li>Type: <b>Short Password</b></li>
<li>
<ul>
<li><code>Cvcn</code></li>
</ul>
</li>
<li>Type: <b>Basic Password</b></li>
<li>
<ul>
<li><code>aaanaaan</code></li>
<li><code>aannaaan</code></li>
<li><code>aaannaaa</code></li>
</ul>
</li>
<li>Type: <b>PIN</b></li>
<li>
<ul>
<li><code>nnnn</code></li>
</ul>
</li>
</ul>
</p>
<p>
By default, Master Password uses the <em>Long Password</em> type for any new passwords. The user is able to choose a different password type, which is normally only done if the site's password policy is incompatible with the output password produced by this type.
</p>
<p>
To create the create the output password, the bytes in the <code>seed</code> are encoded according to the cipher. The first <code>seed</code> byte is used to determine which of the type's ciphers to use for encoding an output password. We take the byte value of the first <code>seed</code> byte modulo the amount of ciphers set for the chosen password type and use the result as a zero-based index in the cipher list for the password type.
</p> </p>
<code><pre> <code><pre>
ciphers = [ "CvcvCvcvnoCvcv", "CvcvnoCvcvCvcv", "CvcvCvcvCvcvno" ] ciphers = [ "CvcvCvcvnoCvcv", "CvcvnoCvcvCvcv", "CvcvCvcvCvcvno" ]
cipher = ciphers[ seed[0] % count( ciphers ) ] cipher = ciphers[ seed[0] % count( ciphers ) ]
</pre></code> </pre></code>
<p> <p>
Now that we know what cipher to use for building our final password, all that's left is to iterate the Now that we know what cipher to use for building our output password, all that's left is to iterate the cipher, and produce a character of password output for each step. When we iterate the cipher (index <code>i</code>), we look in the character group identified by the character (string <code>passChars</code>) in the cipher at index <code>i</code>.
cipher, and produce a character of password output for each step. When you iterate the cipher (<code>i</code>), every </p>
character in the cipher represents a set of possible output characters (<code>passChars</code>). For instance, a <code>C</code> <p>
character in the cipher indicates that we need to choose a capital consonant character. An <code>o</code> The following character groups (<code>passChars</code>) are defined:
character in the cipher indicates that we need to choose an <q>other</q> (symbol) character. Exactly which <ul>
character to choose in that set for the password output depends on the next byte from the <code>seed</code>. <li>Cipher character: <code>V</code></li>
Like before, take the next unused <code>seed</code> byte value modulo the amount of characters in the <li>
set of possible output characters for the cipher iteration and use the result to choose the output <ul>
character (<code>passChar</code>). Repeat until you've iterated the whole cipher. <li><code>AEIOU</code></li>
</ul>
</li>
<li>Cipher character: <code>C</code></li>
<li>
<ul>
<li><code>BCDFGHJKLMNPQRSTVWXYZ</code></li>
</ul>
</li>
<li>Cipher character: <code>v</code></li>
<li>
<ul>
<li><code>aeiou</code></li>
</ul>
</li>
<li>Cipher character: <code>c</code></li>
<li>
<ul>
<li><code>bcdfghjklmnpqrstvwxyz</code></li>
</ul>
</li>
<li>Cipher character: <code>A</code> (<code>= V . C</code>)</li>
<li>
<ul>
<li><code>AEIOUBCDFGHJKLMNPQRSTVWXYZ</code></li>
</ul>
</li>
<li>Cipher character: <code>a</code> (<code>= V . v . C . c</code>)</li>
<li>
<ul>
<li><code>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz</code></li>
</ul>
</li>
<li>Cipher character: <code>n</code></li>
<li>
<ul>
<li><code>0123456789</code></li>
</ul>
</li>
<li>Cipher character: <code>o</code></li>
<li>
<ul>
<li><code>!@#$%^&amp;*()</code></li>
</ul>
</li>
<li>Cipher character: <code>X</code> (<code>= a . n . o</code>)</li>
<li>
<ul>
<li><code>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&amp;*()</code></li>
</ul>
</li>
</ul>
</p>
<p>
We use the <code>seed</code>'s byte value at index <code>i + 1</code> modulo the amount of characters in the character class to determine which character (<code>passChar</code>) in the class to use for the output password at index <code>i</code>.
</p> </p>
<code><pre> <code><pre>
passChar = passChars[ seed[i + 1] % count( passChars ) ] passChar = passChars[ seed[i + 1] % count( passChars ) ]
passWord += passChar passWord[i] = passChar
</pre></code> </pre></code>
<hr /> <hr />

BIN
Site/1/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -5,8 +5,8 @@
<head> <head>
<title>Master Password &mdash; Securing your online life.</title> <title>Master Password &mdash; Securing your online life.</title>
<link rel="icon" href="images/resources/favicon.png" type="image/x-png" /> <link rel="icon" href="img/favicon.png" type="image/x-png" />
<link rel="shortcut icon" href="images/resources/favicon.png" type="image/x-png" /> <link rel="shortcut icon" href="img/favicon.png" type="image/x-png" />
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" /> <meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Exo:100,400,600,900,100italic,400italic,600italic' /> <link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Exo:100,400,600,900,100italic,400italic,600italic' />
@ -26,6 +26,8 @@
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})(); })();
</script> </script>
<!-- Get Satisfaction -->
<!--script type="text/javascript" charset="utf-8"> <!--script type="text/javascript" charset="utf-8">
var is_ssl = ("https:" == document.location.protocol); var is_ssl = ("https:" == document.location.protocol);
var asset_host = is_ssl ? "https://d3rdqalhjaisuu.cloudfront.net/" : "http://d3rdqalhjaisuu.cloudfront.net/"; var asset_host = is_ssl ? "https://d3rdqalhjaisuu.cloudfront.net/" : "http://d3rdqalhjaisuu.cloudfront.net/";
@ -40,6 +42,29 @@
feedback_widget_options.style = "question"; feedback_widget_options.style = "question";
var feedback_widget = new GSFN.feedback_widget(feedback_widget_options); var feedback_widget = new GSFN.feedback_widget(feedback_widget_options);
</script--> </script-->
<!-- UserEcho -->
<script type='text/javascript'>
var _ues = {
host:'support.lyndir.com',
forum:'13031',
lang:'en',
tab_icon_show:false,
tab_corner_radius:5,
tab_font_size:20,
tab_image_hash:'RmVlZGJhY2s%3D',
tab_alignment:'right',
tab_text_color:'#FFFFFF',
tab_bg_color:'#DDDDDD',
tab_hover_color:'#CCCCCC'
};
(function() {
var _ue = document.createElement('script'); _ue.type = 'text/javascript'; _ue.async = true;
_ue.src = ('https:' == document.location.protocol ? 'https://s3.amazonaws.com/' : 'http://') + 'cdn.userecho.com/js/widget-1.4.gz.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(_ue, s);
})();
</script>
</head> </head>
<body id="frontpage"> <body id="frontpage">
<header> <header>

59
Site/1/support.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Master Password &mdash; Securing your online life.</title>
<link rel="icon" href="images/resources/favicon.png" type="image/x-png" />
<link rel="shortcut icon" href="images/resources/favicon.png" type="image/x-png" />
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<meta http-equiv="refresh" content="3; url=http://support.lyndir.com/forum/13031-master-password/">
<link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Exo:100,400,600,900,100italic,400italic,600italic' />
<link rel="stylesheet" type="text/css" href="css/ml-shadows.css" />
<link rel="stylesheet" type="text/css" href="css/screen.css" />
<script src="js/jquery-1.6.1.min.js" type="text/javascript"></script>
<script src="js/functions.js" type="text/javascript"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-90535-15']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<header>
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore.png" /></a>
<h1><a href="index.html"><img class="logo" src="img/iTunesArtwork-Bare.png" /> Master Password</a></h1>
<div class="divider"></div>
</header>
<div id="fixedheader">
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore-small.png" /></a>
<h2><a href="index.html">Master Password</a></h2>
</div>
<!--a href="http://bit.ly/vNN5Zi" onclick="_gaq.push(['_trackPageview', '/outbound/testflight']);" id="ribbon"></a-->
<section>
<h1>Opening the support forum...</h1>
<p>
Stand by while you're being redirected to the Master Password support forum...
</p>
</section>
<footer>
Master Password is a security and productivity product by <a href="http://www.lyndir.com" onclick="_gaq.push(['_trackPageview', '/outbound/lyndir.com']);">Lyndir</a>, &copy; 2011.
</footer>
</body>
</html>

View File

@ -31,7 +31,14 @@
var asset_host = is_ssl ? "https://d3rdqalhjaisuu.cloudfront.net/" : "http://d3rdqalhjaisuu.cloudfront.net/"; var asset_host = is_ssl ? "https://d3rdqalhjaisuu.cloudfront.net/" : "http://d3rdqalhjaisuu.cloudfront.net/";
document.write(unescape("%3Cscript src='" + asset_host + "javascripts/feedback-v2.js' type='text/javascript'%3E%3C/script%3E")); document.write(unescape("%3Cscript src='" + asset_host + "javascripts/feedback-v2.js' type='text/javascript'%3E%3C/script%3E"));
</script> </script>
<!-- Get Satisfaction -->
<!--script type="text/javascript" charset="utf-8"> <!--script type="text/javascript" charset="utf-8">
var is_ssl = ("https:" == document.location.protocol);
var asset_host = is_ssl ? "https://d3rdqalhjaisuu.cloudfront.net/" : "http://d3rdqalhjaisuu.cloudfront.net/";
document.write(unescape("%3Cscript src='" + asset_host + "javascripts/feedback-v2.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript" charset="utf-8">
var feedback_widget_options = {}; var feedback_widget_options = {};
feedback_widget_options.display = "overlay"; feedback_widget_options.display = "overlay";
feedback_widget_options.company = "lyndir"; feedback_widget_options.company = "lyndir";
@ -40,6 +47,29 @@
feedback_widget_options.style = "question"; feedback_widget_options.style = "question";
var feedback_widget = new GSFN.feedback_widget(feedback_widget_options); var feedback_widget = new GSFN.feedback_widget(feedback_widget_options);
</script--> </script-->
<!-- UserEcho -->
<script type='text/javascript'>
var _ues = {
host:'support.lyndir.com',
forum:'13031',
lang:'en',
tab_icon_show:false,
tab_corner_radius:5,
tab_font_size:20,
tab_image_hash:'RmVlZGJhY2s%3D',
tab_alignment:'right',
tab_text_color:'#FFFFFF',
tab_bg_color:'#DDDDDD',
tab_hover_color:'#CCCCCC'
};
(function() {
var _ue = document.createElement('script'); _ue.type = 'text/javascript'; _ue.async = true;
_ue.src = ('https:' == document.location.protocol ? 'https://s3.amazonaws.com/' : 'http://') + 'cdn.userecho.com/js/widget-1.4.gz.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(_ue, s);
})();
</script>
</head> </head>
<body> <body>
<header> <header>