2
0

Compare commits

..

21 Commits

Author SHA1 Message Date
Maarten Billemont
07e55140ac Bump Pearl. 2017-04-29 18:07:06 -04:00
Maarten Billemont
fbbd08790d Pasteboard improvements, UI fixes and site name from pasteboard URL.
[UPDATED]   Timeout after 3 min for other pasteboard copies too.
[FIXED]     Sometimes cell content loading can fail, schedule a retry.
[UPDATED]   Dismiss keyboard when copying content.
[IMPROVED]  Handling of deactivation and reactivation observation.
[ADDED]     When a URL is in the pasteboard, search for the hostname.
2017-04-29 17:50:48 -04:00
Maarten Billemont
fcaa5d1d8c Some improvement to observing user changes. 2017-04-29 15:01:24 -04:00
Maarten Billemont
ea5be8efcb Rewrite handling of collection view and table view reloading for reliability. 2017-04-27 02:22:01 -04:00
Maarten Billemont
c8b4933c3d Expire the password from the clipboard after 3 minutes on iOS 10+. 2017-04-26 22:01:27 -04:00
Maarten Billemont
981ee171ae Update site for 2.5-cli-2 2017-04-22 12:35:47 -04:00
Maarten Billemont
3ed6b93736 Keep tarballs in site directory. 2017-04-22 12:33:46 -04:00
Maarten Billemont
56a515c5ea Improve clean methods. 2017-04-22 12:20:52 -04:00
Maarten Billemont
15ac7a2dbf Improve font size and font scaling fixes. 2017-04-22 12:00:47 -04:00
Maarten Billemont
c5c7999753 Ensure the tree is clean before building the distribution archive. 2017-04-22 11:15:07 -04:00
Maarten Billemont
bb58ed0169 Update In-App Settings Kit. 2017-04-22 10:03:31 -04:00
Maarten Billemont
4545a5c745 Improve readability of some of the smaller and thinner fonts. 2017-04-22 09:52:28 -04:00
Maarten Billemont
da8c7064fe Support for reduced transparency. 2017-04-20 22:29:10 -04:00
Maarten Billemont
d9bd604436 Improve support for import/export headers on iOS/Mac.
[ADDED]     iOS/macOS support for Full Name, Algorithm and Default Type mpsites headers.
2017-04-19 21:58:10 -04:00
Maarten Billemont
c99252809d Disable ADHOC and tester functionality / bypasses. 2017-04-18 20:31:38 -04:00
Maarten Billemont
d704f451a3 Fixed issue causing emergency generator password button to not respond. 2017-04-17 22:27:36 -04:00
Maarten Billemont
2c9ab5d153 Fixed issue when cancelling touchID login. 2017-04-17 22:13:01 -04:00
Maarten Billemont
d5d33da12f Fixed UI issues with passwords list and drop-down animation + support for phrase and name default types.
[FIXED]     Fixed issues with animating changes in the passwords list during certain & multiple events.
[FIXED]     Slightly broken UI prior to drop-down animation & improved animation a bit.
[ADDED]     Phrase & Name default password types.
2017-04-17 21:57:08 -04:00
Maarten Billemont
cbef1a611b Update Mac binary to 2.5-mac-2 2017-04-16 13:03:15 -04:00
Maarten Billemont
0a1f215a1a Style login name, add login generated gear, improve logic for when to show login name. 2017-04-15 10:57:52 -04:00
Maarten Billemont
907d2a8ca6 Fixed key disappearing from NSCache after suspension and not being reloaded from keychain. 2017-04-15 02:28:11 -04:00
47 changed files with 1287 additions and 1091 deletions

View File

@@ -19,7 +19,9 @@
93D3932889B6B4206E66A6D6 /* PearlEMail.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */; };
93D39392DEDA376F93C6C718 /* MPCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39BAA71DE51B4D8A1286C /* MPCell.m */; };
93D3939661CE37180AF7CD6A /* MPStoreViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3957D76F71A652716EECC /* MPStoreViewController.m */; };
93D393AA69A1193401160418 /* UIView+AlphaScale.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39488AB33616661725929 /* UIView+AlphaScale.m */; };
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */; };
93D3942C1B117EE4851AA7B6 /* UIView+Visible.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3952910EDB8E0EBC94BA9 /* UIView+Visible.m */; };
93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */; };
93D394B5036C882B33C71872 /* MPPasswordsSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */; };
93D39508A6814612A5B3C226 /* MPMessageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399B36CDB2004D7C51391 /* MPMessageViewController.m */; };
@@ -28,6 +30,7 @@
93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; };
93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B381350802A194BF332 /* MPAvatarCell.m */; };
93D39577FD8BB0945DB2F0A3 /* MPAlgorithmV3.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39FD9623E8D5571C0AEB3 /* MPAlgorithmV3.m */; };
93D3959696396A91961C6148 /* UIView+AlphaScale.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D392D76C091DEA3319F11D /* UIView+AlphaScale.h */; };
93D395B715D15F2B56F2A2EE /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D392C5A6572DB0EB5B82C8 /* mpw-types.c */; };
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
93D39673DDC085BE72C34D7C /* MPPopdownSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */; };
@@ -44,7 +47,7 @@
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D395105935859D71679931 /* MPOverlayViewController.m */; };
93D399E4BC1E092A8C8B12AE /* NSOrderedSetOrArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39FBF8FCEB4C106272334 /* NSOrderedSetOrArray.h */; };
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */; };
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; };
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadItems.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */; };
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; };
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */; };
@@ -53,8 +56,9 @@
93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; };
93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; };
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; };
93D39BFB5F5F9337F6565DE3 /* UIView+Visible.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39B7B765546B1F1900CB7 /* UIView+Visible.h */; };
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */; };
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadItems.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadItems.m */; };
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; };
93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39CC01630D0421205C4C4 /* MPNavigationController.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
@@ -67,6 +71,22 @@
DA071BF4190187FE00179766 /* empty.png in Resources */ = {isa = PBXBuildFile; fileRef = DA071BF2190187FE00179766 /* empty.png */; };
DA095E75172F4CD8001C948B /* MPLogsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */; };
DA0979171E9A81EE00F0BFE8 /* libsodium.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA0979161E9A81EE00F0BFE8 /* libsodium.a */; };
DA0CC5291EAB99BA009A8ED9 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5071EAB99BA009A8ED9 /* IASKAppSettingsViewController.m */; };
DA0CC52A1EAB99BA009A8ED9 /* IASKAppSettingsWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5091EAB99BA009A8ED9 /* IASKAppSettingsWebViewController.m */; };
DA0CC52B1EAB99BA009A8ED9 /* IASKMultipleValueSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC50B1EAB99BA009A8ED9 /* IASKMultipleValueSelection.m */; };
DA0CC52C1EAB99BA009A8ED9 /* IASKSpecifierValuesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC50D1EAB99BA009A8ED9 /* IASKSpecifierValuesViewController.m */; };
DA0CC52D1EAB99BA009A8ED9 /* IASKSettingsReader.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5111EAB99BA009A8ED9 /* IASKSettingsReader.m */; };
DA0CC52E1EAB99BA009A8ED9 /* IASKSettingsStore.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5131EAB99BA009A8ED9 /* IASKSettingsStore.m */; };
DA0CC52F1EAB99BA009A8ED9 /* IASKSettingsStoreFile.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5151EAB99BA009A8ED9 /* IASKSettingsStoreFile.m */; };
DA0CC5301EAB99BA009A8ED9 /* IASKSettingsStoreUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5171EAB99BA009A8ED9 /* IASKSettingsStoreUserDefaults.m */; };
DA0CC5311EAB99BA009A8ED9 /* IASKSpecifier.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5191EAB99BA009A8ED9 /* IASKSpecifier.m */; };
DA0CC5321EAB99BA009A8ED9 /* IASKPSSliderSpecifierViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC51C1EAB99BA009A8ED9 /* IASKPSSliderSpecifierViewCell.m */; };
DA0CC5331EAB99BA009A8ED9 /* IASKPSTextFieldSpecifierViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC51E1EAB99BA009A8ED9 /* IASKPSTextFieldSpecifierViewCell.m */; };
DA0CC5341EAB99BA009A8ED9 /* IASKSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5201EAB99BA009A8ED9 /* IASKSlider.m */; };
DA0CC5351EAB99BA009A8ED9 /* IASKSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5221EAB99BA009A8ED9 /* IASKSwitch.m */; };
DA0CC5361EAB99BA009A8ED9 /* IASKTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5241EAB99BA009A8ED9 /* IASKTextField.m */; };
DA0CC5371EAB99BA009A8ED9 /* IASKTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5261EAB99BA009A8ED9 /* IASKTextView.m */; };
DA0CC5381EAB99BA009A8ED9 /* IASKTextViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC5281EAB99BA009A8ED9 /* IASKTextViewCell.m */; };
DA24EBAE19DAD08900FF010B /* tip_basic_black_top.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38941711E29700CF925C /* tip_basic_black_top.png */; };
DA24EBAF19DAD08C00FF010B /* tip_basic_black_top@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38951711E29700CF925C /* tip_basic_black_top@2x.png */; };
DA24EBE819DAD6DE00FF010B /* Icon-320.png in Resources */ = {isa = PBXBuildFile; fileRef = DA24EBE619DAD6DE00FF010B /* Icon-320.png */; };
@@ -344,20 +364,6 @@
DAF4EF50190A81E400023C90 /* NSManagedObject+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF4EF4E190A81E400023C90 /* NSManagedObject+Pearl.m */; };
DAF4EF51190A81E400023C90 /* NSManagedObject+Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF4EF4F190A81E400023C90 /* NSManagedObject+Pearl.h */; };
DAFC5656172C573B00CB5CC5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAFC5683172C57EC00CB5CC5 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5665172C57EC00CB5CC5 /* IASKAppSettingsViewController.m */; };
DAFC5684172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5667172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.m */; };
DAFC5685172C57EC00CB5CC5 /* IASKSpecifierValuesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5669172C57EC00CB5CC5 /* IASKSpecifierValuesViewController.m */; };
DAFC5686172C57EC00CB5CC5 /* IASKSettingsReader.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC566D172C57EC00CB5CC5 /* IASKSettingsReader.m */; };
DAFC5687172C57EC00CB5CC5 /* IASKSettingsStore.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC566F172C57EC00CB5CC5 /* IASKSettingsStore.m */; };
DAFC5688172C57EC00CB5CC5 /* IASKSettingsStoreFile.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5671172C57EC00CB5CC5 /* IASKSettingsStoreFile.m */; };
DAFC5689172C57EC00CB5CC5 /* IASKSettingsStoreUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5673172C57EC00CB5CC5 /* IASKSettingsStoreUserDefaults.m */; };
DAFC568A172C57EC00CB5CC5 /* IASKSpecifier.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5675172C57EC00CB5CC5 /* IASKSpecifier.m */; };
DAFC568B172C57EC00CB5CC5 /* IASKPSSliderSpecifierViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5678172C57EC00CB5CC5 /* IASKPSSliderSpecifierViewCell.m */; };
DAFC568C172C57EC00CB5CC5 /* IASKPSTextFieldSpecifierViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC567A172C57EC00CB5CC5 /* IASKPSTextFieldSpecifierViewCell.m */; };
DAFC568D172C57EC00CB5CC5 /* IASKPSTitleValueSpecifierViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC567C172C57EC00CB5CC5 /* IASKPSTitleValueSpecifierViewCell.m */; };
DAFC568E172C57EC00CB5CC5 /* IASKSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC567E172C57EC00CB5CC5 /* IASKSlider.m */; };
DAFC568F172C57EC00CB5CC5 /* IASKSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5680172C57EC00CB5CC5 /* IASKSwitch.m */; };
DAFC5690172C57EC00CB5CC5 /* IASKTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5682172C57EC00CB5CC5 /* IASKTextField.m */; };
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */; };
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */; };
DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */; };
@@ -460,7 +466,7 @@
/* Begin PBXFileReference section */
93D390519405B76CC6A57C4F /* MPCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCell.h; sourceTree = "<group>"; };
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+PearlReloadFromArray.m"; sourceTree = "<group>"; };
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadItems.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+PearlReloadItems.m"; sourceTree = "<group>"; };
93D390A3B351FEF1B9EDAB56 /* mpw-algorithm_v2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v2.c"; sourceTree = "<group>"; };
93D390A99850139D0FF0211E /* mpw-algorithm_v0.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v0.c"; sourceTree = "<group>"; };
93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; };
@@ -471,11 +477,12 @@
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = "<group>"; };
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
93D391AA32F24290C424438E /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadFromArray.h"; sourceTree = "<group>"; };
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadItems.h"; sourceTree = "<group>"; };
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = "<group>"; };
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; };
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; };
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-types.c"; sourceTree = "<group>"; };
93D392D76C091DEA3319F11D /* UIView+AlphaScale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+AlphaScale.h"; sourceTree = "<group>"; };
93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = "<group>"; };
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
93D393BB973253D4BAAC84AA /* PearlEMail.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlEMail.m; sourceTree = "<group>"; };
@@ -483,8 +490,10 @@
93D394077F8FAB8167647187 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; };
93D3942A356B639724157982 /* PearlOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlOverlay.h; sourceTree = "<group>"; };
93D394482BB07F90E8FD1314 /* UIResponder+PearlFirstResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIResponder+PearlFirstResponder.h"; sourceTree = "<group>"; };
93D39488AB33616661725929 /* UIView+AlphaScale.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+AlphaScale.m"; sourceTree = "<group>"; };
93D394D73F5BC92297CE8D7B /* MPAlgorithmV3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV3.h; sourceTree = "<group>"; };
93D395105935859D71679931 /* MPOverlayViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOverlayViewController.m; sourceTree = "<group>"; };
93D3952910EDB8E0EBC94BA9 /* UIView+Visible.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+Visible.m"; sourceTree = "<group>"; };
93D3956915634581E737B38C /* PearlNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlNavigationController.m; sourceTree = "<group>"; };
93D3957D76F71A652716EECC /* MPStoreViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoreViewController.m; sourceTree = "<group>"; };
93D3969393A3A46BD27D7078 /* mpw-algorithm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm.c"; sourceTree = "<group>"; };
@@ -524,6 +533,7 @@
93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUICollectionView.h; sourceTree = "<group>"; };
93D39B381350802A194BF332 /* MPAvatarCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAvatarCell.m; sourceTree = "<group>"; };
93D39B455A71EC98C749E623 /* MPOverlayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOverlayViewController.h; sourceTree = "<group>"; };
93D39B7B765546B1F1900CB7 /* UIView+Visible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Visible.h"; sourceTree = "<group>"; };
93D39BAA71DE51B4D8A1286C /* MPCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCell.m; sourceTree = "<group>"; };
93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSString+MPMarkDown.m"; path = "iOS/NSString+MPMarkDown.m"; sourceTree = "<group>"; };
93D39C426E03358384018E85 /* MPAnswersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnswersViewController.m; sourceTree = "<group>"; };
@@ -609,6 +619,54 @@
DA0979131E9A81EE00F0BFE8 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = "<group>"; };
DA0979141E9A81EE00F0BFE8 /* sodium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sodium.h; sourceTree = "<group>"; };
DA0979161E9A81EE00F0BFE8 /* libsodium.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libsodium.a; sourceTree = "<group>"; };
DA0CC4F61EAB99BA009A8ED9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4F71EAB99BA009A8ED9 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4F81EAB99BA009A8ED9 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4F91EAB99BA009A8ED9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4FA1EAB99BA009A8ED9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4FB1EAB99BA009A8ED9 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4FC1EAB99BA009A8ED9 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4FD1EAB99BA009A8ED9 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4FE1EAB99BA009A8ED9 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC4FF1EAB99BA009A8ED9 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/IASKLocalizable.strings"; sourceTree = "<group>"; };
DA0CC5001EAB99BA009A8ED9 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC5011EAB99BA009A8ED9 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC5021EAB99BA009A8ED9 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC5031EAB99BA009A8ED9 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC5041EAB99BA009A8ED9 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/IASKLocalizable.strings; sourceTree = "<group>"; };
DA0CC5061EAB99BA009A8ED9 /* IASKAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsViewController.h; sourceTree = "<group>"; };
DA0CC5071EAB99BA009A8ED9 /* IASKAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsViewController.m; sourceTree = "<group>"; };
DA0CC5081EAB99BA009A8ED9 /* IASKAppSettingsWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsWebViewController.h; sourceTree = "<group>"; };
DA0CC5091EAB99BA009A8ED9 /* IASKAppSettingsWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsWebViewController.m; sourceTree = "<group>"; };
DA0CC50A1EAB99BA009A8ED9 /* IASKMultipleValueSelection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKMultipleValueSelection.h; sourceTree = "<group>"; };
DA0CC50B1EAB99BA009A8ED9 /* IASKMultipleValueSelection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKMultipleValueSelection.m; sourceTree = "<group>"; };
DA0CC50C1EAB99BA009A8ED9 /* IASKSpecifierValuesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSpecifierValuesViewController.h; sourceTree = "<group>"; };
DA0CC50D1EAB99BA009A8ED9 /* IASKSpecifierValuesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSpecifierValuesViewController.m; sourceTree = "<group>"; };
DA0CC50E1EAB99BA009A8ED9 /* IASKViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKViewController.h; sourceTree = "<group>"; };
DA0CC5101EAB99BA009A8ED9 /* IASKSettingsReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsReader.h; sourceTree = "<group>"; };
DA0CC5111EAB99BA009A8ED9 /* IASKSettingsReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsReader.m; sourceTree = "<group>"; };
DA0CC5121EAB99BA009A8ED9 /* IASKSettingsStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStore.h; sourceTree = "<group>"; };
DA0CC5131EAB99BA009A8ED9 /* IASKSettingsStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStore.m; sourceTree = "<group>"; };
DA0CC5141EAB99BA009A8ED9 /* IASKSettingsStoreFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStoreFile.h; sourceTree = "<group>"; };
DA0CC5151EAB99BA009A8ED9 /* IASKSettingsStoreFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStoreFile.m; sourceTree = "<group>"; };
DA0CC5161EAB99BA009A8ED9 /* IASKSettingsStoreUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStoreUserDefaults.h; sourceTree = "<group>"; };
DA0CC5171EAB99BA009A8ED9 /* IASKSettingsStoreUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStoreUserDefaults.m; sourceTree = "<group>"; };
DA0CC5181EAB99BA009A8ED9 /* IASKSpecifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSpecifier.h; sourceTree = "<group>"; };
DA0CC5191EAB99BA009A8ED9 /* IASKSpecifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSpecifier.m; sourceTree = "<group>"; };
DA0CC51B1EAB99BA009A8ED9 /* IASKPSSliderSpecifierViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKPSSliderSpecifierViewCell.h; sourceTree = "<group>"; };
DA0CC51C1EAB99BA009A8ED9 /* IASKPSSliderSpecifierViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKPSSliderSpecifierViewCell.m; sourceTree = "<group>"; };
DA0CC51D1EAB99BA009A8ED9 /* IASKPSTextFieldSpecifierViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKPSTextFieldSpecifierViewCell.h; sourceTree = "<group>"; };
DA0CC51E1EAB99BA009A8ED9 /* IASKPSTextFieldSpecifierViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKPSTextFieldSpecifierViewCell.m; sourceTree = "<group>"; };
DA0CC51F1EAB99BA009A8ED9 /* IASKSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSlider.h; sourceTree = "<group>"; };
DA0CC5201EAB99BA009A8ED9 /* IASKSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSlider.m; sourceTree = "<group>"; };
DA0CC5211EAB99BA009A8ED9 /* IASKSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSwitch.h; sourceTree = "<group>"; };
DA0CC5221EAB99BA009A8ED9 /* IASKSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSwitch.m; sourceTree = "<group>"; };
DA0CC5231EAB99BA009A8ED9 /* IASKTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKTextField.h; sourceTree = "<group>"; };
DA0CC5241EAB99BA009A8ED9 /* IASKTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKTextField.m; sourceTree = "<group>"; };
DA0CC5251EAB99BA009A8ED9 /* IASKTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKTextView.h; sourceTree = "<group>"; };
DA0CC5261EAB99BA009A8ED9 /* IASKTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKTextView.m; sourceTree = "<group>"; };
DA0CC5271EAB99BA009A8ED9 /* IASKTextViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKTextViewCell.h; sourceTree = "<group>"; };
DA0CC5281EAB99BA009A8ED9 /* IASKTextViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKTextViewCell.m; sourceTree = "<group>"; };
DA24EBB219DAD4D000FF010B /* Icon-60.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60.png"; sourceTree = "<group>"; };
DA24EBB319DAD4D000FF010B /* Icon-60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60@2x.png"; sourceTree = "<group>"; };
DA24EBB419DAD4D000FF010B /* Icon-60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60@3x.png"; sourceTree = "<group>"; };
@@ -1468,35 +1526,6 @@
DAF4EF4E190A81E400023C90 /* NSManagedObject+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObject+Pearl.m"; sourceTree = "<group>"; };
DAF4EF4F190A81E400023C90 /* NSManagedObject+Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSManagedObject+Pearl.h"; sourceTree = "<group>"; };
DAFC5655172C573B00CB5CC5 /* libInAppSettingsKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libInAppSettingsKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAFC5664172C57EC00CB5CC5 /* IASKAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsViewController.h; sourceTree = "<group>"; };
DAFC5665172C57EC00CB5CC5 /* IASKAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsViewController.m; sourceTree = "<group>"; };
DAFC5666172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKAppSettingsWebViewController.h; sourceTree = "<group>"; };
DAFC5667172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKAppSettingsWebViewController.m; sourceTree = "<group>"; };
DAFC5668172C57EC00CB5CC5 /* IASKSpecifierValuesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSpecifierValuesViewController.h; sourceTree = "<group>"; };
DAFC5669172C57EC00CB5CC5 /* IASKSpecifierValuesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSpecifierValuesViewController.m; sourceTree = "<group>"; };
DAFC566A172C57EC00CB5CC5 /* IASKViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKViewController.h; sourceTree = "<group>"; };
DAFC566C172C57EC00CB5CC5 /* IASKSettingsReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsReader.h; sourceTree = "<group>"; };
DAFC566D172C57EC00CB5CC5 /* IASKSettingsReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsReader.m; sourceTree = "<group>"; };
DAFC566E172C57EC00CB5CC5 /* IASKSettingsStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStore.h; sourceTree = "<group>"; };
DAFC566F172C57EC00CB5CC5 /* IASKSettingsStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStore.m; sourceTree = "<group>"; };
DAFC5670172C57EC00CB5CC5 /* IASKSettingsStoreFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStoreFile.h; sourceTree = "<group>"; };
DAFC5671172C57EC00CB5CC5 /* IASKSettingsStoreFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStoreFile.m; sourceTree = "<group>"; };
DAFC5672172C57EC00CB5CC5 /* IASKSettingsStoreUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSettingsStoreUserDefaults.h; sourceTree = "<group>"; };
DAFC5673172C57EC00CB5CC5 /* IASKSettingsStoreUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSettingsStoreUserDefaults.m; sourceTree = "<group>"; };
DAFC5674172C57EC00CB5CC5 /* IASKSpecifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSpecifier.h; sourceTree = "<group>"; };
DAFC5675172C57EC00CB5CC5 /* IASKSpecifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSpecifier.m; sourceTree = "<group>"; };
DAFC5677172C57EC00CB5CC5 /* IASKPSSliderSpecifierViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKPSSliderSpecifierViewCell.h; sourceTree = "<group>"; };
DAFC5678172C57EC00CB5CC5 /* IASKPSSliderSpecifierViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKPSSliderSpecifierViewCell.m; sourceTree = "<group>"; };
DAFC5679172C57EC00CB5CC5 /* IASKPSTextFieldSpecifierViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKPSTextFieldSpecifierViewCell.h; sourceTree = "<group>"; };
DAFC567A172C57EC00CB5CC5 /* IASKPSTextFieldSpecifierViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKPSTextFieldSpecifierViewCell.m; sourceTree = "<group>"; };
DAFC567B172C57EC00CB5CC5 /* IASKPSTitleValueSpecifierViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKPSTitleValueSpecifierViewCell.h; sourceTree = "<group>"; };
DAFC567C172C57EC00CB5CC5 /* IASKPSTitleValueSpecifierViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKPSTitleValueSpecifierViewCell.m; sourceTree = "<group>"; };
DAFC567D172C57EC00CB5CC5 /* IASKSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSlider.h; sourceTree = "<group>"; };
DAFC567E172C57EC00CB5CC5 /* IASKSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSlider.m; sourceTree = "<group>"; };
DAFC567F172C57EC00CB5CC5 /* IASKSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKSwitch.h; sourceTree = "<group>"; };
DAFC5680172C57EC00CB5CC5 /* IASKSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKSwitch.m; sourceTree = "<group>"; };
DAFC5681172C57EC00CB5CC5 /* IASKTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IASKTextField.h; sourceTree = "<group>"; };
DAFC5682172C57EC00CB5CC5 /* IASKTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IASKTextField.m; sourceTree = "<group>"; };
DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlExport.h"; sourceTree = "<group>"; };
DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlExport.m"; sourceTree = "<group>"; };
DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PearlNSArrayFormat.h"; sourceTree = "<group>"; };
@@ -1770,6 +1799,68 @@
path = lib;
sourceTree = "<group>";
};
DA0CC4F41EAB99BA009A8ED9 /* Resources */ = {
isa = PBXGroup;
children = (
DA0CC4F51EAB99BA009A8ED9 /* IASKLocalizable.strings */,
);
path = Resources;
sourceTree = "<group>";
};
DA0CC5051EAB99BA009A8ED9 /* Controllers */ = {
isa = PBXGroup;
children = (
DA0CC5061EAB99BA009A8ED9 /* IASKAppSettingsViewController.h */,
DA0CC5071EAB99BA009A8ED9 /* IASKAppSettingsViewController.m */,
DA0CC5081EAB99BA009A8ED9 /* IASKAppSettingsWebViewController.h */,
DA0CC5091EAB99BA009A8ED9 /* IASKAppSettingsWebViewController.m */,
DA0CC50A1EAB99BA009A8ED9 /* IASKMultipleValueSelection.h */,
DA0CC50B1EAB99BA009A8ED9 /* IASKMultipleValueSelection.m */,
DA0CC50C1EAB99BA009A8ED9 /* IASKSpecifierValuesViewController.h */,
DA0CC50D1EAB99BA009A8ED9 /* IASKSpecifierValuesViewController.m */,
DA0CC50E1EAB99BA009A8ED9 /* IASKViewController.h */,
);
path = Controllers;
sourceTree = "<group>";
};
DA0CC50F1EAB99BA009A8ED9 /* Models */ = {
isa = PBXGroup;
children = (
DA0CC5101EAB99BA009A8ED9 /* IASKSettingsReader.h */,
DA0CC5111EAB99BA009A8ED9 /* IASKSettingsReader.m */,
DA0CC5121EAB99BA009A8ED9 /* IASKSettingsStore.h */,
DA0CC5131EAB99BA009A8ED9 /* IASKSettingsStore.m */,
DA0CC5141EAB99BA009A8ED9 /* IASKSettingsStoreFile.h */,
DA0CC5151EAB99BA009A8ED9 /* IASKSettingsStoreFile.m */,
DA0CC5161EAB99BA009A8ED9 /* IASKSettingsStoreUserDefaults.h */,
DA0CC5171EAB99BA009A8ED9 /* IASKSettingsStoreUserDefaults.m */,
DA0CC5181EAB99BA009A8ED9 /* IASKSpecifier.h */,
DA0CC5191EAB99BA009A8ED9 /* IASKSpecifier.m */,
);
path = Models;
sourceTree = "<group>";
};
DA0CC51A1EAB99BA009A8ED9 /* Views */ = {
isa = PBXGroup;
children = (
DA0CC51B1EAB99BA009A8ED9 /* IASKPSSliderSpecifierViewCell.h */,
DA0CC51C1EAB99BA009A8ED9 /* IASKPSSliderSpecifierViewCell.m */,
DA0CC51D1EAB99BA009A8ED9 /* IASKPSTextFieldSpecifierViewCell.h */,
DA0CC51E1EAB99BA009A8ED9 /* IASKPSTextFieldSpecifierViewCell.m */,
DA0CC51F1EAB99BA009A8ED9 /* IASKSlider.h */,
DA0CC5201EAB99BA009A8ED9 /* IASKSlider.m */,
DA0CC5211EAB99BA009A8ED9 /* IASKSwitch.h */,
DA0CC5221EAB99BA009A8ED9 /* IASKSwitch.m */,
DA0CC5231EAB99BA009A8ED9 /* IASKTextField.h */,
DA0CC5241EAB99BA009A8ED9 /* IASKTextField.m */,
DA0CC5251EAB99BA009A8ED9 /* IASKTextView.h */,
DA0CC5261EAB99BA009A8ED9 /* IASKTextView.m */,
DA0CC5271EAB99BA009A8ED9 /* IASKTextViewCell.h */,
DA0CC5281EAB99BA009A8ED9 /* IASKTextViewCell.m */,
);
path = Views;
sourceTree = "<group>";
};
DA24EBB019DAD4D000FF010B /* ios */ = {
isa = PBXGroup;
children = (
@@ -2865,64 +2956,15 @@
DAFC5662172C57EC00CB5CC5 /* InAppSettingsKit */ = {
isa = PBXGroup;
children = (
DAFC5663172C57EC00CB5CC5 /* Controllers */,
DAFC566B172C57EC00CB5CC5 /* Models */,
DAFC5676172C57EC00CB5CC5 /* Views */,
DA0CC4F41EAB99BA009A8ED9 /* Resources */,
DA0CC5051EAB99BA009A8ED9 /* Controllers */,
DA0CC50F1EAB99BA009A8ED9 /* Models */,
DA0CC51A1EAB99BA009A8ED9 /* Views */,
);
name = InAppSettingsKit;
path = InAppSettingsKit/InAppSettingsKit;
sourceTree = "<group>";
};
DAFC5663172C57EC00CB5CC5 /* Controllers */ = {
isa = PBXGroup;
children = (
DAFC5664172C57EC00CB5CC5 /* IASKAppSettingsViewController.h */,
DAFC5665172C57EC00CB5CC5 /* IASKAppSettingsViewController.m */,
DAFC5666172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.h */,
DAFC5667172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.m */,
DAFC5668172C57EC00CB5CC5 /* IASKSpecifierValuesViewController.h */,
DAFC5669172C57EC00CB5CC5 /* IASKSpecifierValuesViewController.m */,
DAFC566A172C57EC00CB5CC5 /* IASKViewController.h */,
);
path = Controllers;
sourceTree = "<group>";
};
DAFC566B172C57EC00CB5CC5 /* Models */ = {
isa = PBXGroup;
children = (
DAFC566C172C57EC00CB5CC5 /* IASKSettingsReader.h */,
DAFC566D172C57EC00CB5CC5 /* IASKSettingsReader.m */,
DAFC566E172C57EC00CB5CC5 /* IASKSettingsStore.h */,
DAFC566F172C57EC00CB5CC5 /* IASKSettingsStore.m */,
DAFC5670172C57EC00CB5CC5 /* IASKSettingsStoreFile.h */,
DAFC5671172C57EC00CB5CC5 /* IASKSettingsStoreFile.m */,
DAFC5672172C57EC00CB5CC5 /* IASKSettingsStoreUserDefaults.h */,
DAFC5673172C57EC00CB5CC5 /* IASKSettingsStoreUserDefaults.m */,
DAFC5674172C57EC00CB5CC5 /* IASKSpecifier.h */,
DAFC5675172C57EC00CB5CC5 /* IASKSpecifier.m */,
);
path = Models;
sourceTree = "<group>";
};
DAFC5676172C57EC00CB5CC5 /* Views */ = {
isa = PBXGroup;
children = (
DAFC5677172C57EC00CB5CC5 /* IASKPSSliderSpecifierViewCell.h */,
DAFC5678172C57EC00CB5CC5 /* IASKPSSliderSpecifierViewCell.m */,
DAFC5679172C57EC00CB5CC5 /* IASKPSTextFieldSpecifierViewCell.h */,
DAFC567A172C57EC00CB5CC5 /* IASKPSTextFieldSpecifierViewCell.m */,
DAFC567B172C57EC00CB5CC5 /* IASKPSTitleValueSpecifierViewCell.h */,
DAFC567C172C57EC00CB5CC5 /* IASKPSTitleValueSpecifierViewCell.m */,
DAFC567D172C57EC00CB5CC5 /* IASKSlider.h */,
DAFC567E172C57EC00CB5CC5 /* IASKSlider.m */,
DAFC567F172C57EC00CB5CC5 /* IASKSwitch.h */,
DAFC5680172C57EC00CB5CC5 /* IASKSwitch.m */,
DAFC5681172C57EC00CB5CC5 /* IASKTextField.h */,
DAFC5682172C57EC00CB5CC5 /* IASKTextField.m */,
);
path = Views;
sourceTree = "<group>";
};
DAFE45D715039823003ABA7C /* Pearl */ = {
isa = PBXGroup;
children = (
@@ -3072,8 +3114,8 @@
DAFE462515039823003ABA7C /* Resources */,
DA250A16195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.h */,
DA250A15195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m */,
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */,
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */,
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */,
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadItems.m */,
DAFE4A63150399FF003ABA83 /* UIControl+PearlBlocks.h */,
DAFE4A63150399FF003ABA81 /* UIControl+PearlBlocks.m */,
DAFE4A63150399FF003ABA8B /* UIControl+PearlSelect.h */,
@@ -3098,6 +3140,10 @@
DAEFB01C19BCBD9E00525079 /* UIView+LayoutGone.m */,
DAEC85B418E3DD9A007FC0DF /* UIView+Touches.h */,
DAEC85B118E3DD9A007FC0DF /* UIView+Touches.m */,
93D39488AB33616661725929 /* UIView+AlphaScale.m */,
93D392D76C091DEA3319F11D /* UIView+AlphaScale.h */,
93D3952910EDB8E0EBC94BA9 /* UIView+Visible.m */,
93D39B7B765546B1F1900CB7 /* UIView+Visible.h */,
);
path = "Pearl-UIKit";
sourceTree = "<group>";
@@ -3203,9 +3249,11 @@
93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */,
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */,
93D392A8777DC30C11361647 /* UITextView+PearlAttributes.h in Headers */,
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */,
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadItems.h in Headers */,
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */,
93D399E4BC1E092A8C8B12AE /* NSOrderedSetOrArray.h in Headers */,
93D3959696396A91961C6148 /* UIView+AlphaScale.h in Headers */,
93D39BFB5F5F9337F6565DE3 /* UIView+Visible.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3475,6 +3523,8 @@
zh_HK,
zh_TW,
zu,
Base,
"pt-PT",
);
mainGroup = DA5BFA39147E415C00F98B1E;
productRefGroup = DA5BFA45147E415C00F98B1E /* Products */;
@@ -3909,9 +3959,11 @@
DAA141201922FF020032B392 /* PearlTween.m in Sources */,
93D391ECBD9BD2C64115B5DD /* PearlSizedTextView.m in Sources */,
93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */,
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */,
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadItems.m in Sources */,
93D3928D629EA563F9EC4909 /* NSPersistentStore+PearlMigration.m in Sources */,
93D392A33CCE85431E910C7B /* NSOrderedSetOrArray.m in Sources */,
93D393AA69A1193401160418 /* UIView+AlphaScale.m in Sources */,
93D3942C1B117EE4851AA7B6 /* UIView+Visible.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3919,26 +3971,50 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DAFC5683172C57EC00CB5CC5 /* IASKAppSettingsViewController.m in Sources */,
DAFC5684172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.m in Sources */,
DAFC5685172C57EC00CB5CC5 /* IASKSpecifierValuesViewController.m in Sources */,
DAFC5686172C57EC00CB5CC5 /* IASKSettingsReader.m in Sources */,
DAFC5687172C57EC00CB5CC5 /* IASKSettingsStore.m in Sources */,
DAFC5688172C57EC00CB5CC5 /* IASKSettingsStoreFile.m in Sources */,
DAFC5689172C57EC00CB5CC5 /* IASKSettingsStoreUserDefaults.m in Sources */,
DAFC568A172C57EC00CB5CC5 /* IASKSpecifier.m in Sources */,
DAFC568B172C57EC00CB5CC5 /* IASKPSSliderSpecifierViewCell.m in Sources */,
DAFC568C172C57EC00CB5CC5 /* IASKPSTextFieldSpecifierViewCell.m in Sources */,
DAFC568D172C57EC00CB5CC5 /* IASKPSTitleValueSpecifierViewCell.m in Sources */,
DAFC568E172C57EC00CB5CC5 /* IASKSlider.m in Sources */,
DAFC568F172C57EC00CB5CC5 /* IASKSwitch.m in Sources */,
DAFC5690172C57EC00CB5CC5 /* IASKTextField.m in Sources */,
DA0CC52B1EAB99BA009A8ED9 /* IASKMultipleValueSelection.m in Sources */,
DA0CC5321EAB99BA009A8ED9 /* IASKPSSliderSpecifierViewCell.m in Sources */,
DA0CC52D1EAB99BA009A8ED9 /* IASKSettingsReader.m in Sources */,
DA0CC52A1EAB99BA009A8ED9 /* IASKAppSettingsWebViewController.m in Sources */,
DA0CC5291EAB99BA009A8ED9 /* IASKAppSettingsViewController.m in Sources */,
DA0CC5381EAB99BA009A8ED9 /* IASKTextViewCell.m in Sources */,
DA0CC5311EAB99BA009A8ED9 /* IASKSpecifier.m in Sources */,
DA0CC5341EAB99BA009A8ED9 /* IASKSlider.m in Sources */,
DA0CC5351EAB99BA009A8ED9 /* IASKSwitch.m in Sources */,
DA0CC5331EAB99BA009A8ED9 /* IASKPSTextFieldSpecifierViewCell.m in Sources */,
DA0CC52E1EAB99BA009A8ED9 /* IASKSettingsStore.m in Sources */,
DA0CC52C1EAB99BA009A8ED9 /* IASKSpecifierValuesViewController.m in Sources */,
DA0CC5361EAB99BA009A8ED9 /* IASKTextField.m in Sources */,
DA0CC5371EAB99BA009A8ED9 /* IASKTextView.m in Sources */,
DA0CC52F1EAB99BA009A8ED9 /* IASKSettingsStoreFile.m in Sources */,
DA0CC5301EAB99BA009A8ED9 /* IASKSettingsStoreUserDefaults.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
DA0CC4F51EAB99BA009A8ED9 /* IASKLocalizable.strings */ = {
isa = PBXVariantGroup;
children = (
DA0CC4F61EAB99BA009A8ED9 /* Base */,
DA0CC4F71EAB99BA009A8ED9 /* de */,
DA0CC4F81EAB99BA009A8ED9 /* el */,
DA0CC4F91EAB99BA009A8ED9 /* en */,
DA0CC4FA1EAB99BA009A8ED9 /* es */,
DA0CC4FB1EAB99BA009A8ED9 /* fr */,
DA0CC4FC1EAB99BA009A8ED9 /* it */,
DA0CC4FD1EAB99BA009A8ED9 /* ja */,
DA0CC4FE1EAB99BA009A8ED9 /* nl */,
DA0CC4FF1EAB99BA009A8ED9 /* pt-PT */,
DA0CC5001EAB99BA009A8ED9 /* pt */,
DA0CC5011EAB99BA009A8ED9 /* ru */,
DA0CC5021EAB99BA009A8ED9 /* sv */,
DA0CC5031EAB99BA009A8ED9 /* th */,
DA0CC5041EAB99BA009A8ED9 /* tr */,
);
name = IASKLocalizable.strings;
sourceTree = "<group>";
};
DABD3BFA1711E2DC00CF925C /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
@@ -4005,7 +4081,6 @@
GCC_NO_COMMON_BLOCKS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"ADHOC=1",
"$(inherited)",
"NDEBUG=1",
"NS_BLOCK_ASSERTIONS=1",
@@ -4273,7 +4348,6 @@
GCC_NO_COMMON_BLOCKS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"ADHOC=1",
"$(inherited)",
"NDEBUG=1",
"NS_BLOCK_ASSERTIONS=1",

View File

@@ -2568,7 +2568,6 @@
GCC_NO_COMMON_BLOCKS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"ADHOC=1",
"$(inherited)",
"NDEBUG=1",
"NS_BLOCK_ASSERTIONS=1",
@@ -2838,7 +2837,6 @@
GCC_NO_COMMON_BLOCKS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"ADHOC=1",
"$(inherited)",
"NDEBUG=1",
"NS_BLOCK_ASSERTIONS=1",

View File

@@ -58,6 +58,7 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
- (Class)classOfType:(MPSiteType)type;
- (NSArray *)allTypes;
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
- (MPSiteType)defaultType;
- (MPSiteType)nextType:(MPSiteType)type;
- (MPSiteType)previousType:(MPSiteType)type;

View File

@@ -286,6 +286,11 @@ NSOperationQueue *_mpwQueue = nil;
return allTypes;
}
- (MPSiteType)defaultType {
return MPSiteTypeGeneratedLong;
}
- (MPSiteType)nextType:(MPSiteType)type {
switch (type) {
@@ -309,9 +314,9 @@ NSOperationQueue *_mpwQueue = nil;
return MPSiteTypeStoredDevicePrivate;
case MPSiteTypeStoredDevicePrivate:
return MPSiteTypeGeneratedPhrase;
default:
return MPSiteTypeGeneratedLong;
}
return [self defaultType];
}
- (MPSiteType)previousType:(MPSiteType)type {
@@ -430,58 +435,38 @@ NSOperationQueue *_mpwQueue = nil;
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
return PearlAwait( ^(void (^setResult)(id)) {
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
setResult( result_ );
}];
} );
}
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
return PearlAwait( ^(void (^setResult)(id)) {
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
setResult( result_ );
}];
} );
}
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
return PearlAwait( ^(void (^setResult)(id)) {
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
setResult( result_ );
}];
} );
}
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
return PearlAwait( ^(void (^setResult)(id)) {
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
setResult( result_ );
}];
} );
}
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
@@ -498,10 +483,12 @@ NSOperationQueue *_mpwQueue = nil;
else
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
resultBlock( loginName || !loginGenerated? loginName:
[algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
} );
if (!loginGenerated || [loginName length])
resultBlock( loginName );
else
PearlNotMainQueue( ^{
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
} );
}
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
@@ -533,9 +520,8 @@ NSOperationQueue *_mpwQueue = nil;
else
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey];
resultBlock( result );
PearlNotMainQueue( ^{
resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] );
} );
break;
}
@@ -549,9 +535,8 @@ NSOperationQueue *_mpwQueue = nil;
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
resultBlock( result );
PearlNotMainQueue( ^{
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
} );
break;
}
@@ -563,9 +548,8 @@ NSOperationQueue *_mpwQueue = nil;
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
resultBlock( result );
PearlNotMainQueue( ^{
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
} );
break;
}
@@ -584,9 +568,8 @@ NSOperationQueue *_mpwQueue = nil;
else
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey];
resultBlock( result );
PearlNotMainQueue( ^{
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] );
} );
}
@@ -605,9 +588,8 @@ NSOperationQueue *_mpwQueue = nil;
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
algorithm = question.site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey];
resultBlock( result );
PearlNotMainQueue( ^{
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] );
} );
}

View File

@@ -75,7 +75,7 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
// Consumable product.
return NO;
#if ADHOC || DEBUG
#if DEBUG
// All features are unlocked for beta / debug / mac versions.
return YES;
#else

View File

@@ -72,14 +72,23 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
MPKeyOrigin keyOrigin;
NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin );
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery];
if (!keyData) {
id<MPAlgorithm> keyAlgorithm = user.algorithm;
MPKey *key = [[MPKey alloc] initForFullName:user.name withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
return ![algorithm isEqual:keyAlgorithm]? nil:
PearlMainQueueAwait( (id)^{
return [PearlKeyChain dataOfItemForQuery:keyQuery];
} );
} keyOrigin:keyOrigin];
if ([key keyIDForAlgorithm:user.algorithm])
inf( @"Found key in keychain for user: %@", user.userID );
else {
inf( @"No key found in keychain for user: %@", user.userID );
return nil;
key = nil;
}
inf( @"Found key in keychain for user: %@", user.userID );
return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm keyOrigin:keyOrigin];
return key;
}
- (void)storeSavedKeyFor:(MPUserEntity *)user {
@@ -230,28 +239,22 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
NSString *content;
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
__block NSString *masterPassword = nil;
NSString *masterPassword = nil;
#ifdef PEARL_UIKIT
dispatch_group_t recoverPasswordGroup = dispatch_group_create();
dispatch_group_enter( recoverPasswordGroup );
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
site.name )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
masterPassword = PearlAwait( ^(void (^setResult)(id)) {
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
message:PearlString(
@"Your old master password is required to migrate the stored password for %@",
site.name )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ cancelButtonIndex])
// Don't Migrate
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( recoverPasswordGroup );
}
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
dispatch_group_wait( recoverPasswordGroup, DISPATCH_TIME_FOREVER );
setResult( nil );
else
setResult( [alert_ textFieldAtIndex:0].text );
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
} );
#endif
if (!masterPassword)
// Don't Migrate

View File

@@ -35,6 +35,7 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock;
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock;
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
- (void)deleteAndResetStore;

View File

@@ -131,6 +131,25 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return YES;
}
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock {
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
if (!privateManagedObjectContextIfReady)
return nil;
return PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, privateManagedObjectContextIfReady, nil,
^(id host, NSNotification *note) {
NSMutableDictionary *affectedObjects = [NSMutableDictionary new];
for (NSManagedObject *object in note.userInfo[NSInsertedObjectsKey])
affectedObjects[object.objectID] = NSInsertedObjectsKey;
for (NSManagedObject *object in note.userInfo[NSUpdatedObjectsKey])
affectedObjects[object.objectID] = NSUpdatedObjectsKey;
for (NSManagedObject *object in note.userInfo[NSDeletedObjectsKey])
affectedObjects[object.objectID] = NSDeletedObjectsKey;
changedBlock( affectedObjects );
} );
}
- (NSManagedObjectContext *)mainManagedObjectContextIfReady {
[self loadStore];
@@ -196,19 +215,21 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
[mainManagedObjectContext performBlock:^{
@try {
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@", [exception fullDescription] );
}
}];
} );
if ([self.mainManagedObjectContext respondsToSelector:@selector( automaticallyMergesChangesFromParent )]) // iOS 10+
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
else
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainContext, NSNotification *note) {
[mainContext performBlock:^{
@try {
[mainContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@", [exception fullDescription] );
}
}];
} );
// Create a new store coordinator.
@@ -537,12 +558,13 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
// Parse import data.
inf( @"Importing sites." );
__block MPUserEntity *user = nil;
id<MPAlgorithm> importAlgorithm = nil;
NSUInteger importFormat = 0;
__block MPUserEntity *user = nil;
NSUInteger importAvatar = NSNotFound;
NSString *importBundleVersion = nil, *importUserName = nil;
NSData *importKeyID = nil;
NSString *importBundleVersion = nil, *importUserName = nil;
id<MPAlgorithm> importAlgorithm = nil;
MPSiteType importDefaultType = (MPSiteType)0;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *sitesToDelete = [NSMutableSet set];
@@ -573,7 +595,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
if ([headerName isEqualToString:@"Format"]) {
importFormat = (NSUInteger)[headerValue integerValue];
if (importFormat >= [sitePatterns count]) {
err( @"Unsupported import format: %lu", (unsigned long)importFormat );
return MPImportResultInternalError;
}
}
if (([headerName isEqualToString:@"User Name"] || [headerName isEqualToString:@"Full Name"]) && !importUserName) {
importUserName = headerValue;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
@@ -591,21 +621,18 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
user = [users lastObject];
dbg( @"Existing user? %@", [user debugDescription] );
}
if ([headerName isEqualToString:@"Avatar"])
importAvatar = (NSUInteger)[headerValue integerValue];
if ([headerName isEqualToString:@"Key ID"])
importKeyID = [headerValue decodeHex];
if ([headerName isEqualToString:@"Version"]) {
importBundleVersion = headerValue;
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
}
if ([headerName isEqualToString:@"Format"]) {
importFormat = (NSUInteger)[headerValue integerValue];
if (importFormat >= [sitePatterns count]) {
err( @"Unsupported import format: %lu", (unsigned long)importFormat );
return MPImportResultInternalError;
}
}
if ([headerName isEqualToString:@"Avatar"])
importAvatar = (NSUInteger)[headerValue integerValue];
if ([headerName isEqualToString:@"Algorithm"])
importAlgorithm = MPAlgorithmForVersion( (MPAlgorithmVersion)[headerValue integerValue] );
if ([headerName isEqualToString:@"Default Type"])
importDefaultType = (MPSiteType)[headerValue integerValue];
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
@@ -709,6 +736,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
if (user) {
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
if (importDefaultType)
user.defaultType = importDefaultType;
dbg( @"Updating User: %@", [user debugDescription] );
}
else {
@@ -716,6 +745,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
user.name = importUserName;
user.algorithm = MPAlgorithmDefault;
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
user.defaultType = importDefaultType?: user.algorithm.defaultType;
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Created User: %@", [user debugDescription] );
@@ -785,16 +815,16 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
[export appendFormat:@"# \n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"# Format: 1\n"];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
[export appendFormat:@"# User Name: %@\n", activeUser.name];
[export appendFormat:@"# Full Name: %@\n", activeUser.name];
[export appendFormat:@"# Avatar: %lu\n", (unsigned long)activeUser.avatar];
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# Format: 1\n"];
if (revealPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"];
else
[export appendFormat:@"# Passwords: PROTECTED\n"];
[export appendFormat:@"# Algorithm: %d\n", activeUser.algorithm.version];
[export appendFormat:@"# Default Type: %d\n", activeUser.defaultType];
[export appendFormat:@"# Passwords: %@\n", revealPasswords? @"VISIBLE": @"PROTECTED"];
[export appendFormat:@"##\n"];
[export appendFormat:@"#\n"];
[export appendFormat:@"# Last Times Password Login\t Site\tSite\n"];

View File

@@ -16,7 +16,7 @@
- (BOOL)saveToStore {
__block BOOL success = YES;
if ([self hasChanges]) {
if ([self hasChanges])
[self performBlockAndWait:^{
@try {
NSError *error = nil;
@@ -28,7 +28,6 @@
err( @"While saving: %@", [exception fullDescription] );
}
}];
}
return success && (!self.parentContext || [self.parentContext saveToStore]);
}
@@ -237,8 +236,8 @@
// Invalid self.user.defaultType
result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
self.name, self.user.name, (long)self.type, (long)MPSiteTypeGeneratedLong );
self.type = MPSiteTypeGeneratedLong;
self.name, self.user.name, (long)self.type, (long)[self.algorithm defaultType] );
self.type = [self.algorithm defaultType];
return MPFixableResultProblemsFixed;
} );
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
@@ -330,7 +329,7 @@
- (MPSiteType)defaultType {
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType;
}
- (void)setDefaultType:(MPSiteType)aDefaultType {

View File

@@ -28,12 +28,12 @@ typedef NS_ENUM( NSUInteger, MPKeyOrigin ) {
@interface MPKey : NSObject
@property(nonatomic, readonly) NSString *fullName;
@property(nonatomic, readonly) MPKeyOrigin origin;
@property(nonatomic, readonly, copy) NSString *fullName;
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin;
- (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
keyOrigin:(MPKeyOrigin)origin;
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm;

View File

@@ -19,9 +19,9 @@
@interface MPKey()
@property(nonatomic) NSString *fullName;
@property(nonatomic) MPKeyOrigin origin;
@property(nonatomic) NSString *masterPassword;
@property(nonatomic, copy) NSString *fullName;
@property(nonatomic, copy) NSData *( ^keyResolver )(id<MPAlgorithm>);
@end
@@ -31,25 +31,22 @@
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
return [self initForFullName:fullName withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
return [algorithm keyDataForFullName:self.fullName withMasterPassword:masterPassword];
} keyOrigin:MPKeyOriginMasterPassword];
}
- (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
keyOrigin:(MPKeyOrigin)origin {
if (!(self = [super init]))
return nil;
_keyCache = [NSCache new];
self.fullName = fullName;
self.origin = MPKeyOriginMasterPassword;
self.masterPassword = masterPassword;
return self;
}
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin {
if (!(self = [self initForFullName:fullName withMasterPassword:nil]))
return nil;
self.origin = origin;
[_keyCache setObject:keyData forKey:algorithm];
self.fullName = fullName;
self.keyResolver = keyResolver;
return self;
}
@@ -61,15 +58,17 @@
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm {
NSData *keyData = [_keyCache objectForKey:algorithm];
if (keyData)
@synchronized (self) {
NSData *keyData = [_keyCache objectForKey:algorithm];
if (keyData)
return keyData;
keyData = self.keyResolver( algorithm );
if (keyData)
[_keyCache setObject:keyData forKey:algorithm];
return keyData;
keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword];
if (keyData)
[_keyCache setObject:keyData forKey:algorithm];
return keyData;
}
}
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
@@ -80,7 +79,7 @@
- (BOOL)isEqualToKey:(MPKey *)key {
return [self.fullName isEqualToString:key.fullName] && [self.masterPassword isEqualToString:self.masterPassword];
return [[self keyIDForAlgorithm:MPAlgorithmDefault] isEqualToData:[key keyIDForAlgorithm:MPAlgorithmDefault]];
}
- (BOOL)isEqual:(id)object {

View File

@@ -28,10 +28,10 @@
[super windowDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
queue:nil usingBlock:^(NSNotification *note) {
[MPMacAppDelegate get].initialWindowController = nil;
}];
PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, nil, ^(id host, NSNotification *note) {
PearlRemoveNotificationObserversFrom( host );
[MPMacAppDelegate get].initialWindowController = nil;
} );
}
#pragma mark - Actions

View File

@@ -72,7 +72,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
if ([crashlyticsAPIKey length]) {
inf(@"Initializing Crashlytics");
#if defined (DEBUG) || defined (ADHOC)
#if DEBUG
[Crashlytics sharedInstance].debugMode = YES;
#endif
[[Crashlytics sharedInstance] setUserIdentifier:[PearlKeyChain deviceIdentifier]];

View File

@@ -41,40 +41,38 @@
[self replaceFonts:self.window.contentView];
prof_rewind( @"replaceFonts" );
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
PearlAddNotificationObserver( NSWindowDidBecomeKeyNotification, self.window, [NSOperationQueue mainQueue],
^(id host, NSNotification *note) {
prof_new( @"didBecomeKey" );
[self.window makeKeyAndOrderFront:nil];
prof_rewind( @"fadeIn" );
[self updateUser];
prof_finish( @"updateUser" );
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
} );
PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, [NSOperationQueue mainQueue],
^(id host, NSNotification *note) {
PearlRemoveNotificationObservers();
NSWindow *sheet = [self.window attachedSheet];
if (sheet)
[self.window endSheet:sheet];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
} );
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
^(id host, NSNotification *note) {
[self.window close];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateUser];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateUser];
}];
} );
PearlAddNotificationObserver( MPSignedInNotification, nil, [NSOperationQueue mainQueue], ^(id host, NSNotification *note) {
[self updateUser];
} );
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue], ^(id host, NSNotification *note) {
[self updateUser];
} );
[self observeKeyPath:@"sitesController.selection" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
prof_new( @"sitesController.selection" );
[_self updateSelection];
prof_finish( @"updateSelection" );
}];
prof_rewind( @"observers" );
NSSearchFieldCell *siteFieldCell = (NSSearchFieldCell *)self.siteField.cell;
NSSearchFieldCell *siteFieldCell = self.siteField.cell;
siteFieldCell.searchButtonCell = nil;
siteFieldCell.cancelButtonCell = nil;
@@ -549,8 +547,8 @@
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
queryPattern, queryPattern, [MPMacAppDelegate get].activeUserOID];
fetchRequest.predicate =
[NSPredicate predicateWithFormat:@"name LIKE[cd] %@ AND user == %@", queryPattern, [MPMacAppDelegate get].activeUserOID];
prof_rewind( @"fetchRequest" );
NSError *error = nil;

View File

@@ -161,10 +161,9 @@
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[MPGlobalAnswersCell class]]) {
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
[UIPasteboard generalPasteboard].string = ((MPGlobalAnswersCell *)cell).answerField.text;
}
if ([cell isKindOfClass:[MPGlobalAnswersCell class]])
[self copyAnswer:((MPGlobalAnswersCell *)cell).answerField.text];
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
if (!_multiple)
[self setMultiple:YES animated:YES];
@@ -192,6 +191,7 @@
} cancelTitle:@"Cancel" otherTitles:@"Remove Questions", nil];
}
}
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
NSString *body;
if (!_multiple) {
@@ -215,14 +215,30 @@
[PearlEMail sendEMailTo:nil fromVC:self subject:strf( @"Master Password security answers for %@", site.name ) body:body];
}
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]]) {
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
[UIPasteboard generalPasteboard].string = ((MPAnswersQuestionCell *)cell).answerField.text;
}
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]])
[self copyAnswer:((MPAnswersQuestionCell *)cell).answerField.text];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)copyAnswer:(NSString *)answer {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector( setItems:options: )]) {
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: answer } ]
options:@{
UIPasteboardOptionLocalOnly : @NO,
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
}];
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied (3 min)" ) dismissAfter:2];
}
else {
pasteboard.string = answer;
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
}
}
#pragma mark - Private
- (void)updateAnimated:(BOOL)animated {

View File

@@ -49,11 +49,10 @@ const long MPAvatarAdd = 10000;
[super awakeFromNib];
self.alpha = 0;
self.visible = NO;
self.nameContainer.layer.cornerRadius = 5;
self.avatarImageView.hidden = NO;
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
self.avatarImageView.layer.masksToBounds = NO;
self.avatarImageView.backgroundColor = [UIColor clearColor];
@@ -211,7 +210,7 @@ const long MPAvatarAdd = 10000;
[UIView animateWithDuration:animated? 0.5f: 0 delay:0
options:UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.alpha = 1;
self.visible = YES;
if (self.newUser) {
if (self.mode == MPAvatarModeLowered)
@@ -220,6 +219,10 @@ const long MPAvatarAdd = 10000;
self.avatar = arc4random() % MPAvatarCount;
}
self.nameContainer.alpha = self.visibility;
self.avatarImageView.alpha = self.visibility * 0.7f + 0.3f;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
switch (self.mode) {
case MPAvatarModeLowered: {
[self.avatarSizeConstraint updateConstant:
@@ -227,10 +230,9 @@ const long MPAvatarAdd = 10000;
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultLow];
self.nameContainer.alpha = self.visibility;
self.nameContainer.visible = YES;
self.nameContainer.backgroundColor = [UIColor clearColor];
self.avatarImageView.alpha = self.visibility * 0.7f + 0.3f;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
self.avatarImageView.visible = YES;
break;
}
case MPAvatarModeRaisedButInactive: {
@@ -239,10 +241,9 @@ const long MPAvatarAdd = 10000;
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultLow];
self.nameContainer.alpha = self.visibility;
self.nameContainer.visible = YES;
self.nameContainer.backgroundColor = [UIColor clearColor];
self.avatarImageView.alpha = 0;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
self.avatarImageView.visible = NO;
break;
}
case MPAvatarModeRaisedAndActive: {
@@ -251,10 +252,9 @@ const long MPAvatarAdd = 10000;
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
self.nameContainer.alpha = self.visibility;
self.nameContainer.visible = YES;
self.nameContainer.backgroundColor = [UIColor blackColor];
self.avatarImageView.alpha = 1;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
self.avatarImageView.visible = YES;
break;
}
case MPAvatarModeRaisedAndHidden: {
@@ -263,10 +263,9 @@ const long MPAvatarAdd = 10000;
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
self.nameContainer.alpha = 0;
self.nameContainer.visible = NO;
self.nameContainer.backgroundColor = [UIColor blackColor];
self.avatarImageView.alpha = 0;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
self.avatarImageView.visible = NO;
break;
}
case MPAvatarModeRaisedAndMinimized: {
@@ -274,9 +273,9 @@ const long MPAvatarAdd = 10000;
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultHigh + 2];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
self.nameContainer.alpha = 0;
self.nameContainer.visible = NO;
self.nameContainer.backgroundColor = [UIColor blackColor];
self.avatarImageView.alpha = 1;
self.avatarImageView.visible = YES;
break;
}
}
@@ -293,7 +292,7 @@ const long MPAvatarAdd = 10000;
else
self.avatarImageView.backgroundColor = [UIColor clearColor];
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
self.spinner.alpha = self.spinnerActive? 1: 0;
self.spinner.visible = self.spinnerActive;
[self.contentView layoutIfNeeded];
} completion:nil];

View File

@@ -28,17 +28,16 @@
[super viewDidLoad];
_views = [NSArray arrayWithObjects:
self.view0, self.view1, self.view2, self.view3, self.view4, self.view5, self.view6, self.view7, self.view8, self.view9, nil];
_views = @[ self.view0, self.view1, self.view2, self.view3, self.view4, self.view5, self.view6, self.view7, self.view8, self.view9 ];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.viewProgress.hidden = NO;
self.viewProgress.visible = YES;
self.viewProgress.progress = 0;
[_views makeObjectsPerformSelector:@selector( setAlpha: ) withObject:@0];
[_views makeObjectsPerformSelector:@selector( setVisible: ) withObject:@NO];
_nextView = 0;
}
@@ -47,7 +46,7 @@
[super viewDidAppear:animated];
[UIView animateWithDuration:0.3f animations:^{
[_views[_nextView++] setAlpha:1];
[_views[_nextView++] setVisible:YES];
}];
_viewTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 block:^(NSTimer *timer) {
@@ -56,11 +55,11 @@
if (self.viewProgress.progress == 1)
[UIView animateWithDuration:0.3f animations:^{
self.viewProgress.progress = 0;
[_views[_nextView++] setAlpha:1];
[_views[_nextView++] setVisible:YES];
if (_nextView >= [_views count]) {
[_viewTimer invalidate];
self.viewProgress.hidden = YES;
self.viewProgress.visible = NO;
}
}];
} repeats:YES];

View File

@@ -28,7 +28,7 @@
@property(weak, nonatomic) IBOutlet UISegmentedControl *typeControl;
@property(weak, nonatomic) IBOutlet UILabel *counterLabel;
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *activity;
@property(weak, nonatomic) IBOutlet UILabel *passwordLabel;
@property(weak, nonatomic) IBOutlet UIButton *passwordButton;
@property(weak, nonatomic) IBOutlet UIView *tipContainer;
- (IBAction)controlChanged:(UIControl *)control;

View File

@@ -81,22 +81,31 @@
[self updatePassword];
}
- (IBAction)copyPassword:(UITapGestureRecognizer *)recognizer {
- (IBAction)copyPassword:(id)sender {
if (recognizer.state == UIGestureRecognizerStateEnded) {
NSString *sitePassword = self.passwordLabel.text;
if ([sitePassword length]) {
[UIPasteboard generalPasteboard].string = sitePassword;
NSString *sitePassword = [self.passwordButton titleForState:UIControlStateNormal];
if (![sitePassword length])
return;
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector( setItems:options: )])
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: sitePassword } ]
options:@{
UIPasteboardOptionLocalOnly : @NO,
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
}];
else
pasteboard.string = sitePassword;
[UIView animateWithDuration:0.3f animations:^{
self.tipContainer.visible = YES;
} completion:^(BOOL finished) {
PearlMainQueueAfter( 3, ^{
[UIView animateWithDuration:0.3f animations:^{
self.tipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished)
PearlMainQueueAfter( 3, ^{
self.tipContainer.alpha = 0;
} );
self.tipContainer.visible = NO;
}];
}
}
} );
}];
}
#pragma mark - Private
@@ -106,7 +115,7 @@
NSString *fullName = self.fullNameField.text;
NSString *masterPassword = self.masterPasswordField.text;
self.passwordLabel.text = nil;
[self.passwordButton setTitle:nil forState:UIControlStateNormal];
[self.activity startAnimating];
[_emergencyKeyQueue cancelAllOperations];
[_emergencyKeyQueue addOperationWithBlock:^{
@@ -128,7 +137,7 @@
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
self.passwordLabel.text = nil;
[self.passwordButton setTitle:nil forState:UIControlStateNormal];
[self.activity startAnimating];
[_emergencyPasswordQueue cancelAllOperations];
[_emergencyPasswordQueue addOperationWithBlock:^{
@@ -138,7 +147,7 @@
PearlMainQueue( ^{
[self.activity stopAnimating];
self.passwordLabel.text = sitePassword;
[self.passwordButton setTitle:sitePassword forState:UIControlStateNormal];
} );
}];
}

View File

@@ -57,7 +57,7 @@
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
[dismissButton addTarget:self action:@selector( dismissOverlay: ) forControlEvents:UIControlEventTouchUpInside];
dismissButton.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5f];
dismissButton.alpha = 0;
dismissButton.visible = NO;
dismissButton.frame = self.view.bounds;
dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_dismissSegueByButton[[NSValue valueWithNonretainedObject:dismissButton]] =
@@ -89,7 +89,7 @@
[_dismissSegueByButton removeObjectForKey:dismissSegueKey];
[UIView animateWithDuration:0.1f animations:^{
dismissButton.alpha = 0;
dismissButton.visible = NO;
} completion:^(BOOL finished) {
[dismissButton removeFromSuperview];
}];
@@ -127,14 +127,14 @@
CGRectSetY( destinationViewController.view.frame, 100 );
destinationViewController.view.transform = CGAffineTransformMakeScale( 1.2f, 1.2f );
destinationViewController.view.alpha = 0;
destinationViewController.view.visible = NO;
[UIView transitionWithView:containerViewController.view duration:0.3f
options:UIViewAnimationOptionAllowAnimatedContent animations:^{
destinationViewController.view.transform = CGAffineTransformIdentity;
CGRectSetY( destinationViewController.view.frame, 0 );
destinationViewController.view.alpha = 1;
dismissButton.alpha = 1;
destinationViewController.view.visible = YES;
dismissButton.visible = YES;
} completion:^(BOOL finished) {
[destinationViewController didMoveToParentViewController:containerViewController];
[containerViewController setNeedsStatusBarAppearanceUpdate];
@@ -147,7 +147,7 @@
options:UIViewAnimationOptionAllowAnimatedContent animations:^{
CGRectSetY( sourceViewController.view.frame, 100 );
sourceViewController.view.transform = CGAffineTransformMakeScale( 0.8f, 0.8f );
sourceViewController.view.alpha = 0;
sourceViewController.view.visible = NO;
[containerViewController removeDismissButtonForViewController:sourceViewController];
} completion:^(BOOL finished) {
if (finished) {

View File

@@ -28,6 +28,7 @@
@property(nonatomic, strong) IBOutlet UITextField *passwordField;
@property(nonatomic, strong) IBOutlet UIView *loginNameContainer;
@property(nonatomic, strong) IBOutlet UITextField *loginNameField;
@property(nonatomic, strong) IBOutlet UILabel *loginNameGenerated;
@property(nonatomic, strong) IBOutlet UILabel *strengthLabel;
@property(nonatomic, strong) IBOutlet UILabel *counterLabel;
@property(nonatomic, strong) IBOutlet UIButton *counterButton;
@@ -199,7 +200,7 @@
atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
if (textField == self.loginNameField)
self.loginNameButton.titleLabel.alpha = [self.loginNameField.text length] || self.loginNameField.enabled? 0: 1;
self.loginNameButton.hidden = [self.loginNameField.attributedText length] || self.loginNameField.enabled;
}
- (IBAction)textFieldDidChange:(UITextField *)textField {
@@ -224,7 +225,7 @@
if (textField == self.passwordField || textField == self.loginNameField) {
textField.enabled = NO;
NSString *text = textField.text;
NSString *text = [textField.attributedText string]?: textField.text;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *site = [self siteInContext:context];
@@ -235,10 +236,8 @@
if ([site.algorithm savePassword:text toSite:site usingKey:[MPiOSAppDelegate get].key])
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
}
else if (textField == self.loginNameField &&
((site.loginGenerated && ![text length]) ||
(!site.loginGenerated && ![text isEqualToString:site.loginName]))) {
if (site.loginGenerated || !([site.loginName isEqualToString:text] || (!text && !site.loginName))) {
else if (textField == self.loginNameField) {
if (![text isEqualToString:[site.algorithm resolveLoginForSite:site usingKey:[MPiOSAppDelegate get].key]]) {
site.loginGenerated = NO;
site.loginName = text;
@@ -282,12 +281,12 @@
[self setMode:MPPasswordCellModePassword animated:YES];
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
[PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
initSheet:^(UIActionSheet *sheet) {
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
for (NSNumber *typeNumber in [MPAlgorithmDefault allTypes]) {
for (NSNumber *typeNumber in [mainSite.algorithm allTypes]) {
MPSiteType type = (MPSiteType)[typeNumber unsignedIntegerValue];
NSString *typeName = [MPAlgorithmDefault nameOfType:type];
NSString *typeName = [mainSite.algorithm nameOfType:type];
if (type == mainSite.type)
[sheet addButtonWithTitle:strf( @"● %@", typeName )];
else
@@ -297,7 +296,8 @@
if (buttonIndex == [sheet cancelButtonIndex])
return;
MPSiteType type = (MPSiteType)[[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
MPSiteType type = (MPSiteType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?:
mainSite.user.defaultType?: mainSite.algorithm.defaultType;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *site = [self siteInContext:context];
@@ -493,10 +493,12 @@
- (void)updateAnimated:(BOOL)animated {
Weakify( self );
if (![NSThread isMainThread]) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:animated];
}];
} );
return;
}
@@ -507,10 +509,10 @@
self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue];
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0;
self.loginNameField.textColor = [UIColor colorWithHexString:mainSite.loginGenerated? @"5E636D": @"6D5E63"];
self.modeButton.alpha = self.transientSite? 0: settingsMode? 0.5f: 0.1f;
self.counterLabel.alpha = self.counterButton.alpha = mainSite.type & MPSiteTypeClassGenerated? 0.5f: 0;
self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length];
self.modeButton.visible = !self.transientSite;
self.modeButton.alpha = settingsMode? 0.5f: 0.1f;
self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPSiteTypeClassGenerated;
self.modeButton.selected = settingsMode;
self.strengthLabel.gone = !settingsMode;
self.modeScrollView.scrollEnabled = !self.transientSite;
@@ -520,13 +522,21 @@
[self.passwordField resignFirstResponder];
}
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
[self.loginNameButton setTitle:@"Tap to generate username or use pencil to save one" forState:UIControlStateNormal];
[self.loginNameButton setTitle:@"Tap here to ⚙ generate username or the pencil to type one" forState:UIControlStateNormal];
else
[self.loginNameButton setTitle:@"Tap the pencil to save a username" forState:UIControlStateNormal];
[self.loginNameButton setTitle:@"Tap the pencil to type a username" forState:UIControlStateNormal];
// Site Name
[self updateSiteName:mainSite];
// Site Counter
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
// Site Login Name
self.loginNameField.enabled = self.passwordField.enabled = //
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
// Site Password
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
self.passwordField.attributedPlaceholder = stra(
@@ -534,14 +544,23 @@
mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{
NSForegroundColorAttributeName: [UIColor whiteColor]
} );
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
// Calculate Fields
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *site = [self siteInContext:context];
MPKey *key = [MPiOSAppDelegate get].key;
if (!key)
if (!key) {
wrn( @"Could not load cell content: key unavailable." );
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:YES];
} );
return;
}
BOOL loginGenerated = site.loginGenerated;
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPSiteTypeGeneratedLong;
MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
if (self.transientSite && transientType & MPSiteTypeClassGenerated)
password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType:transientType
withCounter:1 usingKey:key];
@@ -559,13 +578,15 @@
BOOL requiresExplicitMigration = site.requiresExplicitMigration;
PearlMainQueue( ^{
self.loginNameField.text = loginName;
self.passwordField.text = password;
self.strengthLabel.text = timeToCrackString;
self.loginNameButton.titleLabel.alpha = [loginName length] || self.loginNameField.enabled? 0: 1;
self.loginNameGenerated.hidden = !loginGenerated;
self.loginNameField.attributedText =
strarm( stra( loginName?: @"", self.siteNameLabel.textAttributes ), NSParagraphStyleAttributeName, nil );
self.loginNameButton.hidden = [loginName length] || self.loginNameField.enabled;
if (![password length]) {
self.indicatorView.alpha = 1;
self.indicatorView.hidden = NO;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
@@ -575,7 +596,7 @@
}];
}
else if (requiresExplicitMigration) {
self.indicatorView.alpha = 1;
self.indicatorView.hidden = NO;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
@@ -585,17 +606,15 @@
}];
}
else
self.indicatorView.alpha = 0;
self.indicatorView.hidden = YES;
} );
}];
// Site Counter
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
// Site Login Name
self.loginNameField.enabled = self.passwordField.enabled = //
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
}]) {
wrn( @"Could not load cell content: store unavailable." );
PearlMainQueueOperation( ^{
Strongify( self );
[self updateAnimated:YES];
} );
}
[self.contentView layoutIfNeeded];
}];
@@ -616,8 +635,9 @@
range:NSMakeRange( s, [self.fuzzyGroups[f] length] )];
}
[attributedSiteName appendAttributedString:stra(
strf( @" - %@", self.transientSite? @"Tap to create": [site.algorithm shortNameOfType:site.type] ), @{} )];
if (self.transientSite)
[attributedSiteName appendAttributedString:stra( @" Tap to create", @{} )];
self.siteNameLabel.attributedText = attributedSiteName;
}
@@ -629,8 +649,21 @@
return NO;
PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Password Copied" ) dismissAfter:2];
[UIPasteboard generalPasteboard].string = password;
[self.window endEditing:YES];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector(setItems:options:)]) {
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: password } ]
options:@{
UIPasteboardOptionLocalOnly : @NO,
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
}];
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Password Copied (3 min)" ) dismissAfter:2];
}
else {
pasteboard.string = password;
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Password Copied" ) dismissAfter:2];
}
} );
[site use];
@@ -646,8 +679,10 @@
return NO;
PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
[self.window endEditing:YES];
[UIPasteboard generalPasteboard].string = loginName;
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
} );
[site use];

View File

@@ -34,7 +34,7 @@
@property(assign, nonatomic) BOOL active;
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void ( ^ )(BOOL finished))completion;
- (void)updatePasswords;
- (void)reloadPasswords;
- (IBAction)dismissPopdown:(id)sender;

View File

@@ -25,6 +25,8 @@
#import "MPAnswersViewController.h"
#import "MPMessageViewController.h"
static const NSString *MPTransientPasswordItem = @"MPTransientPasswordItem";
typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
MPPasswordsBadNameTip = 1 << 0,
};
@@ -32,7 +34,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
@property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar;
@property(nonatomic, readonly) NSString *query;
@end
@@ -42,10 +43,9 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
UIColor *_backgroundColor;
UIColor *_darkenedBackgroundColor;
__weak UIViewController *_popdownVC;
BOOL _showTransientItem;
NSUInteger _transientItem;
NSCharacterSet *_siteNameAcceptableCharactersSet;
NSArray *_fuzzyGroups;
NSMutableArray<NSMutableArray *> *_passwordCollectionSections;
}
#pragma mark - Life
@@ -61,7 +61,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
_backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_transientItem = NSNotFound;
_passwordCollectionSections = [NSMutableArray new];
self.view.backgroundColor = [UIColor clearColor];
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
@@ -81,7 +81,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[self registerObservers];
[self updateConfigKey:nil];
[self updatePasswords];
}
- (void)viewDidAppear:(BOOL)animated {
@@ -138,30 +137,42 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
return CGSizeMake( itemWidth, 100 );
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
insetForSectionAtIndex:(NSInteger)section {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
UIEdgeInsets occludedInsets = [self.passwordCollectionView occludedInsets];
UIEdgeInsets insets = layout.sectionInset;
if (section == 0)
insets.top += occludedInsets.top;
if (section == collectionView.numberOfSections - 1)
insets.bottom += occludedInsets.bottom;
return insets;
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return [self.fetchedResultsController.sections count];
return [_passwordCollectionSections count];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
if (![MPiOSAppDelegate get].activeUserOID || !_fetchedResultsController)
return 0;
NSUInteger objects = ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[section]).numberOfObjects;
_transientItem = _showTransientItem? objects: NSNotFound;
return objects + (_showTransientItem? 1: 0);
return [_passwordCollectionSections[(NSUInteger)section] count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
[cell setFuzzyGroups:_fuzzyGroups];
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
[cell setSite:[self.fetchedResultsController objectAtIndexPath:indexPath] animated:NO];
else
id item = _passwordCollectionSections[(NSUInteger)indexPath.section][(NSUInteger)indexPath.item];
if ([item isKindOfClass:[MPSiteEntity class]])
[cell setSite:item animated:NO];
else // item == MPTransientPasswordItem
[cell setTransientSite:self.query animated:NO];
return cell;
@@ -178,41 +189,14 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (controller == _fetchedResultsController) {
@try {
[self.passwordCollectionView performBatchUpdates:^{
[self fetchedItemsDidUpdate];
switch (type) {
case NSFetchedResultsChangeInsert:
[self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
break;
case NSFetchedResultsChangeDelete:
[self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]];
break;
case NSFetchedResultsChangeMove:
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
break;
case NSFetchedResultsChangeUpdate:
[self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]];
break;
}
} completion:nil];
}
@catch (NSException *exception) {
wrn( @"While updating password cells: %@", [exception fullDescription] );
[self.passwordCollectionView reloadData];
}
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (controller == _fetchedResultsController)
[self.passwordCollectionView reloadData];
PearlMainQueue( ^{
[self.passwordCollectionView updateDataSource:_passwordCollectionSections
toSections:[self createPasswordCollectionSections]
reloadItems:nil completion:nil];
} );
}
#pragma mark - UISearchBarDelegate
@@ -244,7 +228,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
if (_passwordsDismissRecognizer)
[self.view removeGestureRecognizer:_passwordsDismissRecognizer];
[self updatePasswords];
[self reloadPasswords];
[UIView animateWithDuration:0.3f animations:^{
self.passwordCollectionView.backgroundColor = _backgroundColor;
}];
@@ -268,7 +252,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
if ([[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length])
[self showTips:MPPasswordsBadNameTip];
[self updatePasswords];
[self reloadPasswords];
}
}
@@ -278,62 +262,78 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPPasswordsBadNameTip)
self.badNameTipContainer.alpha = 1;
self.badNameTipContainer.visible = YES;
} completion:^(BOOL finished) {
if (finished)
PearlMainQueueAfter( 5, ^{
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPPasswordsBadNameTip)
self.badNameTipContainer.alpha = 0;
}];
} );
PearlMainQueueAfter( 5, ^{
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPPasswordsBadNameTip)
self.badNameTipContainer.visible = NO;
}];
} );
}];
}
- (void)fetchedItemsDidUpdate {
- (NSMutableArray<NSMutableArray *> *)createPasswordCollectionSections {
NSString *query = self.query;
_showTransientItem = [query length] > 0;
NSUInteger objects = ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[0]).numberOfObjects;
if (_showTransientItem && objects == 1 &&
[[[self.fetchedResultsController.fetchedObjects firstObject] name] isEqualToString:query])
_showTransientItem = NO;
if ([self.passwordCollectionView numberOfSections] > 0) {
if (!_showTransientItem && _transientItem != NSNotFound)
[self.passwordCollectionView deleteItemsAtIndexPaths:
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
else if (_showTransientItem && _transientItem == NSNotFound)
[self.passwordCollectionView insertItemsAtIndexPaths:
@[ [NSIndexPath indexPathForItem:objects inSection:0] ]];
else if (_transientItem != NSNotFound)
[self.passwordCollectionView reloadItemsAtIndexPaths:
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
BOOL needTransientItem = [query length] > 0;
NSArray<id<NSFetchedResultsSectionInfo>> *sectionInfos = [self.fetchedResultsController sections];
NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:[sectionInfos count]];
for (id<NSFetchedResultsSectionInfo> sectionInfo in sectionInfos) {
NSArray<MPSiteEntity *> *sites = [sectionInfo.objects copy];
[sections addObject:sites];
if (needTransientItem)
for (MPSiteEntity *site in sites)
if ([site.name isEqualToString:query]) {
needTransientItem = NO;
break;
}
}
if (needTransientItem)
[sections addObject:@[ MPTransientPasswordItem ]];
return sections;
}
- (void)registerObservers {
static NSRegularExpression *bareHostRE = nil;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
bareHostRE = [NSRegularExpression regularExpressionWithPattern:@"([^\\.]+\\.[^\\.]+)$" options:0 error:nil];
} );
PearlRemoveNotificationObservers();
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
self.passwordSelectionContainer.alpha = 0;
} );
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
[self updatePasswords];
self.passwordSelectionContainer.visible = NO;
} );
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
NSURL *pasteboardURL = nil;
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector( hasURLs )])
pasteboardURL = pasteboard.hasURLs? pasteboard.URL: nil;
else
pasteboardURL = [NSURL URLWithString:pasteboard.string];
if (pasteboardURL.host)
self.query = NSNullToNil( [pasteboardURL.host firstMatchGroupsOfExpression:bareHostRE][0] );
else
[self reloadPasswords];
[UIView animateWithDuration:0.7f animations:^{
self.passwordSelectionContainer.alpha = 1;
self.passwordSelectionContainer.visible = YES;
}];
} );
PearlAddNotificationObserver( MPSignedOutNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
PearlMainQueue( ^{
_fetchedResultsController = nil;
self.passwordsSearchBar.text = nil;
[self.passwordCollectionView reloadData];
self->_fetchedResultsController = nil;
self.query = nil;
} );
} );
PearlAddNotificationObserver( MPCheckConfigNotification, nil, nil,
@@ -345,25 +345,23 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
self->_fetchedResultsController = nil;
PearlMainQueue( ^{
[self.passwordCollectionView reloadData];
} );
[self reloadPasswords];
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
PearlMainQueue( ^{
[self updatePasswords];
[self reloadPasswords];
[self registerObservers];
} );
} );
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
if (mainContext)
PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
if (![[MPiOSAppDelegate get] activeUserInContext:note.object])
[[MPiOSAppDelegate get] signOutAnimated:YES];
} );
[MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) {
// TODO: either move this into the app delegate or remove the duplicate signOutAnimated: call from the app delegate.
if (![[MPiOSAppDelegate get] activeUserInContext:mainContext])
[[MPiOSAppDelegate get] signOutAnimated:YES];
}];
}];
}
- (void)updateConfigKey:(NSString *)key {
@@ -374,81 +372,38 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[self.passwordCollectionView reloadData];
}
- (void)updatePasswords {
- (void)reloadPasswords {
NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID;
if (!activeUserOID) {
PearlMainQueue( ^{
self.passwordsSearchBar.text = nil;
[self.passwordCollectionView reloadData];
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) animated:YES];
} );
return;
}
static NSRegularExpression *fuzzyRE;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
} );
NSString *queryString = self.query;
NSString *queryPattern;
if ([queryString length] < 13)
queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
else
// If query is too long, a wildcard per character makes the CoreData fetch take excessively long.
queryPattern = strf( @"*%@*", queryString );
NSMutableArray *fuzzyGroups = [NSMutableArray arrayWithCapacity:[queryString length]];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
}];
_fuzzyGroups = fuzzyGroups;
[self.fetchedResultsController.managedObjectContext performBlock:^{
NSArray *oldSectionInfos = [self.fetchedResultsController sections];
NSMutableArray *oldSections = [[NSMutableArray alloc] initWithCapacity:[oldSectionInfos count]];
for (id<NSFetchedResultsSectionInfo> sectionInfo in oldSectionInfos)
[oldSections addObject:[sectionInfo.objects copy]];
static NSRegularExpression *fuzzyRE;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
} );
NSString *queryString = self.query;
NSString *queryPattern = [[queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1"]
stringByAppendingString:@"*"];
NSMutableArray *fuzzyGroups = [NSMutableArray new];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
}];
_fuzzyGroups = fuzzyGroups;
NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate =
[NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
queryPattern, queryPattern, activeUserOID];
[NSPredicate predicateWithFormat:@"name LIKE[cd] %@ AND user == %@", queryPattern, [MPiOSAppDelegate get].activeUserOID];
if (![self.fetchedResultsController performFetch:&error])
err( @"Couldn't fetch sites: %@", [error fullDescription] );
PearlMainQueue( ^{
@try {
[self.passwordCollectionView performBatchUpdates:^{
[self fetchedItemsDidUpdate];
NSInteger fromSections = self.passwordCollectionView.numberOfSections;
NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView];
for (NSInteger section = 0; section < MAX( toSections, fromSections ); ++section) {
if (section >= fromSections)
[self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
else if (section >= toSections)
[self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
else if (section < [oldSections count])
[self.passwordCollectionView reloadItemsFromArray:oldSections[section]
toArray:[[self.fetchedResultsController sections][section] objects]
inSection:section];
else
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:section]];
}
} completion:^(BOOL finished) {
if (finished)
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top )
animated:YES];
for (MPPasswordCell *cell in self.passwordCollectionView.visibleCells)
[cell setFuzzyGroups:_fuzzyGroups];
}];
}
@catch (NSException *exception) {
wrn( @"While updating password cells: %@", [exception fullDescription] );
[self.passwordCollectionView reloadData];
}
[self.passwordCollectionView updateDataSource:_passwordCollectionSections
toSections:[self createPasswordCollectionSections]
reloadItems:@[ MPTransientPasswordItem ] completion:^(BOOL finished) {
for (MPPasswordCell *cell in self.passwordCollectionView.visibleCells)
[cell setFuzzyGroups:_fuzzyGroups];
}];
} );
}];
}
@@ -460,18 +415,24 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
- (void)setQuery:(NSString *)query {
self.passwordsSearchBar.text = [query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
[self reloadPasswords];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (!_fetchedResultsController) {
_showTransientItem = NO;
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
fetchRequest.sortDescriptors = @[
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
];
fetchRequest.fetchBatchSize = 10;
_fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:mainContext
sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
}];
[self registerObservers];

View File

@@ -22,8 +22,6 @@
@implementation MPPopdownSegue {
}
static char UnwindingObserverKey;
- (void)perform {
MPPasswordsViewController *passwordsVC;
@@ -39,19 +37,20 @@ static char UnwindingObserverKey;
[passwordsVC.popdownContainer addConstraintsWithVisualFormats:@[ @"H:|[popdownView]|", @"V:|[popdownView]|" ] options:0
metrics:nil views:NSDictionaryOfVariableBindings( popdownView )];
[UIView animateWithDuration:0.3f animations:^{
[[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
} completion:^(BOOL finished) {
[popdownVC didMoveToParentViewController:passwordsVC];
[passwordsVC.popdownToTopConstraint layoutIfNeeded];
id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:popdownVC
destination:passwordsVC] perform];
}];
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, observer, OBJC_ASSOCIATION_RETAIN );
}];
[UIView animateWithDuration:0.6f delay:0 usingSpringWithDamping:0.75f initialSpringVelocity:1
options:UIViewAnimationOptionCurveEaseOut animations:^{
[[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
} completion:^(BOOL finished) {
[popdownVC didMoveToParentViewController:passwordsVC];
PearlAddNotificationObserverTo( popdownVC, MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
^(id host, NSNotification *note) {
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:popdownVC destination:passwordsVC]
perform];
} );
}];
}
else {
popdownVC = self.sourceViewController;
@@ -59,16 +58,16 @@ static char UnwindingObserverKey;
passwordsVC = (id)passwordsVC.parentViewController);
NSAssert( passwordsVC, @"Couldn't find passwords VC to pop back to." );
[[NSNotificationCenter defaultCenter] removeObserver:objc_getAssociatedObject( popdownVC, &UnwindingObserverKey )];
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, nil, OBJC_ASSOCIATION_RETAIN );
PearlRemoveNotificationObserversFrom( popdownVC );
[popdownVC willMoveToParentViewController:nil];
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{
[[passwordsVC.popdownToTopConstraint updatePriority:UILayoutPriorityDefaultHigh] layoutIfNeeded];
} completion:^(BOOL finished) {
[popdownVC.view removeFromSuperview];
[popdownVC removeFromParentViewController];
}];
[UIView animateWithDuration:0.4f delay:0 options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionOverrideInheritedDuration
animations:^{
[[passwordsVC.popdownToTopConstraint updatePriority:UILayoutPriorityDefaultHigh] layoutIfNeeded];
} completion:^(BOOL finished) {
[popdownVC.view removeFromSuperview];
[popdownVC removeFromParentViewController];
}];
}
}

View File

@@ -29,8 +29,10 @@
@property(weak, nonatomic) IBOutlet UITableViewCell *exportCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *checkInconsistencies;
@property(weak, nonatomic) IBOutlet UIImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UISegmentedControl *generatedTypeControl;
@property(weak, nonatomic) IBOutlet UISegmentedControl *generated1TypeControl;
@property(weak, nonatomic) IBOutlet UISegmentedControl *generated2TypeControl;
@property(weak, nonatomic) IBOutlet UISegmentedControl *storedTypeControl;
@property(weak, nonatomic) IBOutlet UILabel *passwordTypeExample;
- (IBAction)previousAvatar:(id)sender;
- (IBAction)nextAvatar:(id)sender;

View File

@@ -55,12 +55,24 @@
- (void)reload {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:activeUser.defaultType];
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:activeUser.defaultType];
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%lu", (unsigned long)activeUser.avatar )];
self.savePasswordSwitch.on = activeUser.saveKey;
self.touchIDSwitch.on = activeUser.touchID;
self.touchIDSwitch.enabled = self.savePasswordSwitch.on && [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductTouchID];
MPSiteType defaultType = activeUser.defaultType;
self.generated1TypeControl.selectedSegmentIndex = [self generated1SegmentIndexForType:defaultType];
self.generated2TypeControl.selectedSegmentIndex = [self generated2SegmentIndexForType:defaultType];
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType];
PearlNotMainQueue( ^{
NSString *examplePassword = nil;
if (defaultType & MPSiteTypeClassGenerated)
examplePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:@"test" ofType:defaultType
withCounter:1 usingKey:[MPiOSAppDelegate get].key];
PearlMainQueue( ^{
self.passwordTypeExample.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil;
} );
} );
}
#pragma mark - UITableViewDelegate
@@ -88,14 +100,18 @@
[self dismissPopup];
[[MPiOSAppDelegate get] signOutAnimated:YES];
}
if (cell == self.feedbackCell)
[[MPiOSAppDelegate get] showFeedbackWithLogs:YES forVC:self];
if (cell == self.exportCell)
[[MPiOSAppDelegate get] showExportForVC:self];
if (cell == self.showHelpCell) {
MPPasswordsViewController *passwordsVC = [self dismissPopup];
[passwordsVC performSegueWithIdentifier:@"guide" sender:self];
}
if (cell == self.checkInconsistencies)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
if ([[MPiOSAppDelegate get] findAndFixInconsistenciesSaveInContext:context] == MPFixableResultNoProblems)
@@ -140,11 +156,13 @@
} );
}];
if (sender == self.generatedTypeControl || sender == self.storedTypeControl) {
if (sender == self.generatedTypeControl)
if (sender == self.generated1TypeControl || sender == self.generated2TypeControl || sender == self.storedTypeControl) {
if (sender != self.generated1TypeControl)
self.generated1TypeControl.selectedSegmentIndex = -1;
if (sender != self.generated2TypeControl)
self.generated2TypeControl.selectedSegmentIndex = -1;
if (sender != self.storedTypeControl)
self.storedTypeControl.selectedSegmentIndex = -1;
else if (sender == self.storedTypeControl)
self.generatedTypeControl.selectedSegmentIndex = -1;
MPSiteType defaultType = [self typeForSelectedSegment];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
@@ -226,10 +244,17 @@
- (MPSiteType)typeForSelectedSegment {
NSInteger selectedGeneratedIndex = self.generatedTypeControl.selectedSegmentIndex;
NSInteger selectedGenerated1Index = self.generated1TypeControl.selectedSegmentIndex;
NSInteger selectedGenerated2Index = self.generated2TypeControl.selectedSegmentIndex;
NSInteger selectedStoredIndex = self.storedTypeControl.selectedSegmentIndex;
switch (selectedGeneratedIndex) {
switch (selectedGenerated1Index) {
case 0:
return MPSiteTypeGeneratedPhrase;
case 1:
return MPSiteTypeGeneratedName;
default:
switch (selectedGenerated2Index) {
case 0:
return MPSiteTypeGeneratedMaximum;
case 1:
@@ -250,13 +275,26 @@
case 1:
return MPSiteTypeStoredDevicePrivate;
default:
Throw( @"unsupported selected type index: generated=%ld, stored=%ld", (long)selectedGeneratedIndex,
(long)selectedStoredIndex );
Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld",
(long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex );
}
}
}
}
- (NSInteger)generatedSegmentIndexForType:(MPSiteType)type {
- (NSInteger)generated1SegmentIndexForType:(MPSiteType)type {
switch (type) {
case MPSiteTypeGeneratedPhrase:
return 0;
case MPSiteTypeGeneratedName:
return 1;
default:
return -1;
}
}
- (NSInteger)generated2SegmentIndexForType:(MPSiteType)type {
switch (type) {
case MPSiteTypeGeneratedMaximum:

View File

@@ -73,10 +73,10 @@ PearlEnum( MPDevelopmentFuelConsumption,
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
[self reloadCellsHiding:self.allCellsBySection[0] showing:@[ self.loadingCell ]];
[self updateCellsHiding:self.allCellsBySection[0] showing:@[ self.loadingCell ]];
[self.allCellsBySection[0] enumerateObjectsUsingBlock:^(MPStoreProductCell *cell, NSUInteger idx, BOOL *stop) {
if ([cell isKindOfClass:[MPStoreProductCell class]]) {
cell.purchasedIndicator.alpha = 0;
cell.purchasedIndicator.visible = NO;
[cell.activityIndicator stopAnimating];
}
}];
@@ -257,7 +257,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
}
[hideCells removeObjectsInArray:showCells];
[self reloadCellsHiding:hideCells showing:showCells];
[self updateCellsHiding:hideCells showing:showCells];
}
- (void)updateFuel {
@@ -313,7 +313,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
BOOL purchased = [[MPiOSAppDelegate get] isFeatureUnlocked:productIdentifier];
NSInteger quantity = [self quantityForProductIdentifier:productIdentifier];
cell.priceLabel.text = purchased? @"": [self.currencyFormatter stringFromNumber:@([product.price floatValue] * quantity)];
cell.purchasedIndicator.alpha = purchased? 1: 0;
cell.purchasedIndicator.visible = purchased;
}
- (NSInteger)quantityForProductIdentifier:(NSString *)productIdentifier {

View File

@@ -33,7 +33,7 @@
- (void)viewWillAppear:(BOOL)animated {
inf( @"Type selection will appear" );
self.recommendedTipContainer.alpha = 0;
self.recommendedTipContainer.visible = NO;
[super viewWillAppear:animated];
}
@@ -42,15 +42,13 @@
if ([[MPiOSConfig get].firstRun boolValue])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.recommendedTipContainer.alpha = 1;
self.recommendedTipContainer.visible = YES;
} completion:^(BOOL finished) {
if (finished) {
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC) ), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{
self.recommendedTipContainer.alpha = 0;
}];
} );
}
PearlMainQueueAfter( 5, ^{
[UIView animateWithDuration:0.2f animations:^{
self.recommendedTipContainer.visible = NO;
}];
} );
}];
[super viewDidAppear:animated];
@@ -89,11 +87,11 @@
if ([selectedSite isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)selectedSite).counter;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
PearlNotMainQueue( ^{
NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType
withCounter:counter usingKey:[MPiOSAppDelegate get].key];
dispatch_async( dispatch_get_main_queue(), ^{
PearlMainQueue( ^{
[(UITextField *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:2] setText:typeContent];
} );
} );

View File

@@ -58,6 +58,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
@implementation MPUsersViewController {
NSString *_masterPasswordChoice;
NSOperationQueue *_afterUpdates;
__weak id _contextChangedObserver;
}
- (void)viewDidLoad {
@@ -76,7 +77,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
self.avatarCollectionView.allowsMultipleSelection = YES;
[self.entryField addTarget:self action:@selector( textFieldEditingChanged: ) forControlEvents:UIControlEventEditingChanged];
self.preferencesTipContainer.alpha = 0;
self.preferencesTipContainer.visible = NO;
[self setActive:YES animated:NO];
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"tipped.thanks"])
@@ -87,16 +88,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
[super viewWillAppear:animated];
self.userSelectionContainer.alpha = 0;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
PearlRemoveNotificationObservers();
[self.marqueeTipTimer invalidate];
self.userSelectionContainer.visible = NO;
}
- (void)viewDidAppear:(BOOL)animated {
@@ -130,6 +122,15 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
((MPWebViewController *)segue.destinationViewController).initialURL = [NSURL URLWithString:@"http://thanks.lhunath.com"];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self removeObservers];
[self.marqueeTipTimer invalidate];
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidEndEditing:(UITextField *)textField {
@@ -220,6 +221,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
if (isNew) {
user = [MPUserEntity insertNewObjectInContext:context];
user.algorithm = MPAlgorithmDefault;
user.defaultType = user.algorithm.defaultType;
user.avatar = newUserAvatar;
user.name = newUserName;
}
@@ -464,25 +466,23 @@ referenceSizeForFooterInSection:(NSInteger)section {
- (void)deleteUser:(NSManagedObjectID *)userID {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity
*user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_)
MPUserEntity *user = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user)
return;
[context deleteObject:user_];
[context deleteObject:user];
[context saveToStore];
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
}];
}
- (void)resetUser:(NSManagedObjectID *)userID avatar:(MPAvatarCell *)avatarCell {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_)
MPUserEntity *user = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user)
return;
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
[[MPiOSAppDelegate get] changeMasterPasswordFor:user saveInContext:context didResetBlock:^{
PearlMainQueue( ^{
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
@@ -497,25 +497,25 @@ referenceSizeForFooterInSection:(NSInteger)section {
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPUsersThanksTip)
self.thanksTipContainer.alpha = 1;
self.thanksTipContainer.visible = YES;
if (showTips & MPUsersAvatarTip)
self.avatarTipContainer.alpha = 1;
self.avatarTipContainer.visible = YES;
if (showTips & MPUsersMasterPasswordTip)
self.entryTipContainer.alpha = 1;
self.entryTipContainer.visible = YES;
if (showTips & MPUsersPreferencesTip)
self.preferencesTipContainer.alpha = 1;
self.preferencesTipContainer.visible = YES;
} completion:^(BOOL finished) {
if (finished)
PearlMainQueueAfter( 5, ^{
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPUsersThanksTip)
self.thanksTipContainer.alpha = 0;
self.thanksTipContainer.visible = NO;
if (showTips & MPUsersAvatarTip)
self.avatarTipContainer.alpha = 0;
self.avatarTipContainer.visible = NO;
if (showTips & MPUsersMasterPasswordTip)
self.entryTipContainer.alpha = 0;
self.entryTipContainer.visible = NO;
if (showTips & MPUsersPreferencesTip)
self.preferencesTipContainer.alpha = 0;
self.preferencesTipContainer.visible = NO;
}];
} );
}];
@@ -538,14 +538,14 @@ referenceSizeForFooterInSection:(NSInteger)section {
return;
[UIView animateWithDuration:timer? 0.5f: 0 animations:^{
self.marqueeButton.alpha = 0;
self.marqueeButton.visible = NO;
} completion:^(BOOL finished) {
if (!finished)
return;
[self.marqueeButton setTitle:nextMarqueeString forState:UIControlStateNormal];
[UIView animateWithDuration:timer? 0.5f: 0 animations:^{
self.marqueeButton.alpha = 0.5f;
self.marqueeButton.visible = YES;
}];
}];
}
@@ -588,8 +588,6 @@ referenceSizeForFooterInSection:(NSInteger)section {
- (void)updateAvatarVisibility {
self.previousAvatarButton.alpha = 0;
self.nextAvatarButton.alpha = 0;
for (NSIndexPath *indexPath in self.avatarCollectionView.indexPathsForVisibleItems) {
MPAvatarCell *cell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath];
[self updateVisibilityForAvatar:cell atIndexPath:indexPath animated:NO];
@@ -633,10 +631,10 @@ referenceSizeForFooterInSection:(NSInteger)section {
CGFloat visibility = MAX( 0, MIN( 1, 1 - ABS( current / (max / 2) - 1 ) ) );
[cell setVisibility:visibility animated:animated];
if (cell.newUser) {
self.previousAvatarButton.alpha = cell.mode == MPAvatarModeRaisedAndActive? visibility * 0.7f: 0;
self.nextAvatarButton.alpha = cell.mode == MPAvatarModeRaisedAndActive? visibility * 0.7f: 0;
}
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.nextAvatarButton.visible = self.previousAvatarButton.visible = cell.newUser && cell.mode == MPAvatarModeRaisedAndActive;
self.nextAvatarButton.alpha = self.previousAvatarButton.alpha = visibility * 0.7f;
}];
}
- (void)afterUpdatesMainQueue:(void ( ^ )(void))block {
@@ -646,18 +644,24 @@ referenceSizeForFooterInSection:(NSInteger)section {
}];
}
- (void)registerObservers {
- (void)removeObservers {
[self removeKeyPathObservers];
PearlRemoveNotificationObservers();
[[NSNotificationCenter defaultCenter] removeObserver:_contextChangedObserver];
}
- (void)registerObservers {
[self removeObservers];
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
[_self updateAvatarVisibility];
}];
PearlRemoveNotificationObservers();
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
self.userSelectionContainer.alpha = 0;
self.userSelectionContainer.visible = NO;
} );
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
@@ -666,7 +670,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
[UIView animateWithDuration:0.5f animations:^{
self.userSelectionContainer.alpha = 1;
self.userSelectionContainer.visible = YES;
}];
} );
PearlAddNotificationObserver( UIKeyboardWillShowNotification, nil, [NSOperationQueue mainQueue],
@@ -676,36 +680,31 @@ referenceSizeForFooterInSection:(NSInteger)section {
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
} );
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
[UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.alpha = mainContext? 1: 0;
}];
if (mainContext && self.storeLoadingActivity.isAnimating)
[self.storeLoadingActivity stopAnimating];
if (!mainContext && !self.storeLoadingActivity.isAnimating)
[self.storeLoadingActivity startAnimating];
if ((_contextChangedObserver = [MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
if ([[[affectedObjects allKeys] filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(NSManagedObjectID *objectID, NSDictionary *bindings) {
return [objectID.entity.name isEqualToString:NSStringFromClass( [MPUserEntity class] )];
}]] count])
[self reloadUsers];
}]))
[UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.visible = YES;
[self.storeLoadingActivity stopAnimating];
}];
else
[UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.visible = NO;
[self.storeLoadingActivity startAnimating];
}];
if (mainContext)
PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, mainContext, nil,
^(MPUsersViewController *self, NSNotification *note) {
NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey];
NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey];
if ([[NSSetUnion( insertedObjects, deletedObjects )
filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject isKindOfClass:[MPUserEntity class]];
}]] count])
[self reloadUsers];
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
^(MPUsersViewController *self, NSNotification *note) {
self.userIDs = nil;
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
^(MPUsersViewController *self, NSNotification *note) {
PearlMainQueue( ^{
[self registerObservers];
[self reloadUsers];
} );
[self registerObservers];
[self reloadUsers];
} );
}
@@ -763,7 +762,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
[UIView animateWithDuration:0.3f animations:^{
self.userSelectionContainer.alpha = 1;
self.userSelectionContainer.visible = YES;
}];
} );
}
@@ -828,8 +827,8 @@ referenceSizeForFooterInSection:(NSInteger)section {
switch (activeUserState) {
case MPActiveUserStateNone: {
self.avatarCollectionView.scrollEnabled = YES;
self.entryContainer.alpha = 0;
self.footerContainer.alpha = 1;
self.entryContainer.visible = NO;
self.footerContainer.visible = YES;
break;
}
case MPActiveUserStateLogin:
@@ -837,15 +836,15 @@ referenceSizeForFooterInSection:(NSInteger)section {
case MPActiveUserStateMasterPasswordChoice:
case MPActiveUserStateMasterPasswordConfirmation: {
self.avatarCollectionView.scrollEnabled = NO;
self.entryContainer.alpha = 1;
self.footerContainer.alpha = 1;
self.entryContainer.visible = YES;
self.footerContainer.visible = YES;
requestFirstResponder = YES;
break;
}
case MPActiveUserStateMinimized: {
self.avatarCollectionView.scrollEnabled = NO;
self.entryContainer.alpha = 0;
self.footerContainer.alpha = 0;
self.entryContainer.visible = NO;
self.footerContainer.visible = NO;
break;
}
}

View File

@@ -36,7 +36,7 @@
self.webNavigationItem.title = self.initialURL.host;
self.webView.alpha = 0;
self.webView.visible = NO;
[self.webView loadRequest:[[NSURLRequest alloc] initWithURL:self.initialURL]];
}
@@ -75,7 +75,7 @@
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[UIView animateWithDuration:0.3 animations:^{
self.webView.alpha = 1;
self.webView.visible = YES;
}];
[self.webNavigationItem setLeftBarButtonItem:[webView canGoBack]? [[UIBarButtonItem alloc]

View File

@@ -19,13 +19,13 @@
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "IASKSettingsReader.h"
#import "MPStoreViewController.h"
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@property(nonatomic, strong) UIDocumentInteractionController *interactionController;
@property(nonatomic) UIBackgroundTaskIdentifier task;
@end
@implementation MPiOSAppDelegate
@@ -53,7 +53,7 @@
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
if ([crashlyticsAPIKey length]) {
inf( @"Initializing Crashlytics" );
#if defined (DEBUG) || defined (ADHOC)
#if DEBUG
[Crashlytics sharedInstance].debugMode = YES;
#endif
[[Crashlytics sharedInstance] setUserIdentifier:[PearlKeyChain deviceIdentifier]];
@@ -89,18 +89,6 @@
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, nil, ^(id self, NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
} );
#ifdef ADHOC
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
@"Thank you for taking the time to test Master Password.\n\n"
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
@"Contact me directly at:\n"
@"lhunath@lyndir.com\n"
@"Or report detailed issues at:\n"
@"https://youtrack.lyndir.com\n"
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#endif
}
@catch (id exception) {
err( @"During Config Test: %@", exception );
@@ -164,26 +152,24 @@
return NO;
// Arbitrary URL to mpsites data.
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSError *error;
NSURLResponse *response;
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error];
if (error)
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
if (!importedSitesData) {
[PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@", [error localizedDescription]?: error )];
return;
}
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
if (error)
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
if (!importedSitesData) {
[PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@",
[error localizedDescription]?: error )];
return;
}
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
if (!importedSitesString) {
[PearlAlert showError:@"Master Password couldn't understand the import file."];
return;
}
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
if (!importedSitesString) {
[PearlAlert showError:@"Master Password couldn't understand the import file."];
return;
}
[self importSites:importedSitesString];
} );
[self importSites:importedSitesString];
}] resume];
return YES;
}
@@ -199,57 +185,33 @@
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
dispatch_group_t importPasswordGroup = dispatch_group_create();
dispatch_group_enter( importPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
return PearlAwait( ^(void (^setResult)(id)) {
[PearlAlert showAlertWithTitle:@"Import File's Master Password"
message:strf( @"%@'s export was done using a different master password.\n"
@"Enter that master password to unlock the exported data.", userName )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( importPasswordGroup );
}
if (buttonIndex_ == [alert_ cancelButtonIndex])
setResult( nil );
else
setResult( [alert_ textFieldAtIndex:0].text );
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
} );
dispatch_group_wait( importPasswordGroup, DISPATCH_TIME_FOREVER );
return masterPassword;
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
__block NSString *masterPassword = nil;
dispatch_group_t userPasswordGroup = dispatch_group_create();
dispatch_group_enter( userPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
return PearlAwait( (id)^(void (^setResult)(id)) {
[PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName )
message:strf( @"Imports %lu sites, overwriting %lu.",
(unsigned long)importCount, (unsigned long)deleteCount )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( userPasswordGroup );
}
if (buttonIndex_ == [alert_ cancelButtonIndex])
setResult( nil );
else
setResult( [alert_ textFieldAtIndex:0].text );
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
} );
dispatch_group_wait( userPasswordGroup, DISPATCH_TIME_FOREVER );
return masterPassword;
}];
switch (result) {
@@ -283,6 +245,44 @@
if (![[MPiOSConfig get].rememberLogin boolValue])
[self signOutAnimated:NO];
// self.task = [application beginBackgroundTaskWithExpirationHandler:^{
// [application endBackgroundTask:self.task];
// dbg( @"background expiring" );
// }];
// PearlNotMainQueueOperation( ^{
// NSString *pbstring = [UIPasteboard generalPasteboard].string;
// while (YES) {
// NSString *newString = [UIPasteboard generalPasteboard].string;
// if (![newString isEqualToString:pbstring]) {
// dbg( @"pasteboard changed to: %@", newString );
// pbstring = newString;
// NSURL *url = [NSURL URLWithString:pbstring];
// if (url) {
// NSString *siteName = [url host];
// }
// MPKey *key = [MPiOSAppDelegate get].key;
// if (key)
// [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
// NSFetchRequest<MPSiteEntity *>
// *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
// fetchRequest.sortDescriptors = @[
// [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
// ];
// fetchRequest.fetchBatchSize = 2;
// fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(name LIKE[cd] %@) AND user == %@", siteName,
// [[MPiOSAppDelegate get] activeUserOID]];
// NSError *error = nil;
// NSArray<MPSiteEntity *> *results = [fetchRequest execute:&error];
// dbg( @"site search, error: %@, results:\n%@", error, results );
// if ([results count]) {
// [UIPasteboard generalPasteboard].string = [[results firstObject] resolvePasswordUsingKey:key];
// }
// }];
// }
// [NSThread sleepForTimeInterval:5];
// }
// } );
[super applicationDidEnterBackground:application];
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,4 +5,4 @@ set -e
cd "${BASH_SOURCE%/*}"
rm -vfr lib/*/{.unpacked,.patched,src} lib/include
rm -vfr {core,cli}{*.o,*.dSYM} mpw mpw-bench mpw-tests
rm -vfr {core,cli,.}/{*.o,*.dSYM} mpw mpw-bench mpw-tests

View File

@@ -11,19 +11,24 @@ mpwArchive=mpw-$commit.tar.gz
[[ -e $mpwArchive ]] && echo >&2 "WARNING: $mpwArchive already exists. Will overwrite."
read -n1 -p "Will prepare and release $mpwArchive. Press a key to continue or ^C to abort."
echo "Cleaning .."
( git clean -ffdx . && cd core && git clean -ffdx . )
echo "Creating archive $mpwArchive .."
git ls-files -z . | xargs -0 tar -Lcvzf "$mpwArchive"
echo "Creating archive signature $mpwArchive.sig .."
gpg --detach-sign "$mpwArchive"
echo "Installing archive and signature in current site .."
cd ../../public/site/current
ln -sf "../../../platform-independent/cli-c/$mpwArchive"; [[ -e $_ ]]
ln -sf "../../../platform-independent/cli-c/$mpwArchive.sig"; [[ -e $_ ]]
mv "../../../platform-independent/cli-c/$mpwArchive" .; [[ -e $_ ]]
mv "../../../platform-independent/cli-c/$mpwArchive.sig" .; [[ -e $_ ]]
ln -sf "$mpwArchive" "masterpassword-cli.tar.gz"; [[ -e $_ ]]
ln -sf "$mpwArchive.sig" "masterpassword-cli.tar.gz.sig"; [[ -e $_ ]]
echo
echo "Done. Don't forget to publish the site."
echo "package: $mpwArchive"
echo "signature: $mpwArchive.sig"
echo "Done. Ready to publish the site."
echo " package: $mpwArchive"
echo " signature: $mpwArchive.sig"
echo " url: https://ssl.masterpasswordapp.com/$mpwArchive"

View File

@@ -1 +1 @@
mpw-2.5-cli-1-0-gb01e370f.tar.gz
mpw-2.5-cli-2-0-g3ed6b937.tar.gz

View File

@@ -1 +1 @@
mpw-2.5-cli-1-0-gb01e370f.tar.gz.sig
mpw-2.5-cli-2-0-g3ed6b937.tar.gz.sig

View File

@@ -1 +0,0 @@
../../../platform-independent/cli-c/mpw-2.1-cli1-0-g10f1001.tar.gz

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../platform-independent/cli-c/mpw-2.1-cli2-2-g82c96dd.tar.gz

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../platform-independent/cli-c/mpw-2.1-cli3-0-g438daf2.tar.gz

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../platform-independent/cli-c/mpw-2.1-cli4-0-gf6b2287.tar.gz

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../platform-independent/cli-c/mpw-2.5-cli-1-0-gb01e370f.tar.gz

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../platform-independent/cli-c/mpw-2.5-cli-1-0-gb01e370f.tar.gz.sig

Binary file not shown.