diff --git a/External/Pearl b/External/Pearl index ed66e1e8..ff1e29fc 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit ed66e1e8c426aadc98ba4317920808a53e661bdd +Subproject commit ff1e29fc3a4c2ee7b08eb686d06f29a11466bbe5 diff --git a/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword-iOS.xcodeproj/project.pbxproj index 9b4678dd..02424583 100644 --- a/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */; }; 93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; }; 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; }; + 93D399B873AF89808151D2F5 /* MPAppsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390DABAE4368E90E37340 /* MPAppsViewController.m */; }; + 93D39BCE5F69D8EBE7E9F6EC /* MPAppViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E070BD3F45B3045A1DA /* MPAppViewController.m */; }; 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */; }; 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; }; @@ -33,6 +35,10 @@ DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; }; DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; }; DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; }; + DA350A0615F11F9400C14A8E /* pull-down.png in Resources */ = {isa = PBXBuildFile; fileRef = DA350A0215F11F9400C14A8E /* pull-down.png */; }; + DA350A0715F11F9400C14A8E /* pull-down@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA350A0315F11F9400C14A8E /* pull-down@2x.png */; }; + DA350A0815F11F9400C14A8E /* pull-up.png in Resources */ = {isa = PBXBuildFile; fileRef = DA350A0415F11F9400C14A8E /* pull-up.png */; }; + DA350A0915F11F9400C14A8E /* pull-up@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA350A0515F11F9400C14A8E /* pull-up@2x.png */; }; DA3EF17B15A47744003ABF4E /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */; }; DA3EF17C15A47744003ABF4E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA48147E415C00F98B1E /* UIKit.framework */; }; DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; @@ -108,6 +114,12 @@ DA5BFA4F147E415C00F98B1E /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4E147E415C00F98B1E /* CoreData.framework */; }; DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */ = {isa = PBXBuildFile; fileRef = DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */; }; DA600C2815056428008E9AB6 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA600C2715056427008E9AB6 /* MPConfig.m */; }; + DA6061D415F20C0900097266 /* book.png in Resources */ = {isa = PBXBuildFile; fileRef = DA6061CE15F20C0900097266 /* book.png */; }; + DA6061D515F20C0900097266 /* book@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA6061CF15F20C0900097266 /* book@2x.png */; }; + DA6061D615F20C0900097266 /* page-deblock.png in Resources */ = {isa = PBXBuildFile; fileRef = DA6061D015F20C0900097266 /* page-deblock.png */; }; + DA6061D715F20C0900097266 /* page-deblock@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA6061D115F20C0900097266 /* page-deblock@2x.png */; }; + DA6061D815F20C0900097266 /* page-gorillas.png in Resources */ = {isa = PBXBuildFile; fileRef = DA6061D215F20C0900097266 /* page-gorillas.png */; }; + DA6061D915F20C0900097266 /* page-gorillas@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA6061D315F20C0900097266 /* page-gorillas@2x.png */; }; DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; }; DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; @@ -966,17 +978,21 @@ /* Begin PBXFileReference section */ 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = ""; }; + 93D390DABAE4368E90E37340 /* MPAppsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppsViewController.m; sourceTree = ""; }; 93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV0.m; sourceTree = ""; }; 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = ""; }; 93D393BB973253D4BAAC84AA /* PearlEMail.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlEMail.m; sourceTree = ""; }; 93D394077F8FAB8167647187 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; }; 93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = ""; }; + 93D396E57F8AB8BCF00ADFF6 /* MPAppViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppViewController.h; sourceTree = ""; }; + 93D397CC23446E7E66640D82 /* MPAppsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppsViewController.h; sourceTree = ""; }; 93D398E394E311C545E0A057 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = ""; }; 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = ""; }; 93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV0.h; sourceTree = ""; }; 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = ""; }; 93D39C68AFA48A13015E4FAC /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = ""; }; 93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV1.h; sourceTree = ""; }; + 93D39E070BD3F45B3045A1DA /* MPAppViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppViewController.m; sourceTree = ""; }; 93D39E81EFABC6085AC8AE69 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = ""; }; 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV1.m; sourceTree = ""; }; 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlEMail.h; sourceTree = ""; }; @@ -1002,6 +1018,10 @@ DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = ""; }; DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = ""; }; DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = ""; }; + DA350A0215F11F9400C14A8E /* pull-down.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pull-down.png"; sourceTree = ""; }; + DA350A0315F11F9400C14A8E /* pull-down@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pull-down@2x.png"; sourceTree = ""; }; + DA350A0415F11F9400C14A8E /* pull-up.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pull-up.png"; sourceTree = ""; }; + DA350A0515F11F9400C14A8E /* pull-up@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pull-up@2x.png"; sourceTree = ""; }; DA3EF17915A47744003ABF4E /* Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; DA3EF18015A47744003ABF4E /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; @@ -1154,6 +1174,12 @@ DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_Key.h; sourceTree = ""; }; DA600C2615056427008E9AB6 /* MPConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MPConfig.h; path = MasterPassword/MPConfig.h; sourceTree = SOURCE_ROOT; }; DA600C2715056427008E9AB6 /* MPConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MPConfig.m; path = MasterPassword/MPConfig.m; sourceTree = SOURCE_ROOT; }; + DA6061CE15F20C0900097266 /* book.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = book.png; sourceTree = ""; }; + DA6061CF15F20C0900097266 /* book@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "book@2x.png"; sourceTree = ""; }; + DA6061D015F20C0900097266 /* page-deblock.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "page-deblock.png"; sourceTree = ""; }; + DA6061D115F20C0900097266 /* page-deblock@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "page-deblock@2x.png"; sourceTree = ""; }; + DA6061D215F20C0900097266 /* page-gorillas.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "page-gorillas.png"; sourceTree = ""; }; + DA6061D315F20C0900097266 /* page-gorillas@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "page-gorillas@2x.png"; sourceTree = ""; }; DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios.a"; sourceTree = ""; }; DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = ""; }; @@ -2403,6 +2429,7 @@ DA5BFA50147E415C00F98B1E /* MasterPassword */ = { isa = PBXGroup; children = ( + DAB8D43E15036BCF00CED3BC /* iOS */, 93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */, 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */, 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */, @@ -2431,7 +2458,6 @@ DA600C2615056427008E9AB6 /* MPConfig.h */, DA600C2715056427008E9AB6 /* MPConfig.m */, DAB8D45915036BCF00CED3BC /* MPTypes.h */, - DAB8D43E15036BCF00CED3BC /* iOS */, ); path = MasterPassword; sourceTree = ""; @@ -2585,6 +2611,10 @@ children = ( DAB8D9B11503757D00CED3BC /* Supporting Files */, DAB8D44215036BCF00CED3BC /* MainStoryboard_iPhone.storyboard */, + 93D397CC23446E7E66640D82 /* MPAppsViewController.h */, + 93D390DABAE4368E90E37340 /* MPAppsViewController.m */, + 93D396E57F8AB8BCF00ADFF6 /* MPAppViewController.h */, + 93D39E070BD3F45B3045A1DA /* MPAppViewController.m */, DAB8D44815036BCF00CED3BC /* MPiOSConfig.h */, DAB8D44915036BCF00CED3BC /* MPiOSConfig.m */, DAB8D44615036BCF00CED3BC /* MPAppDelegate.h */, @@ -2609,6 +2639,16 @@ DAB8D46F15036BF600CED3BC /* Resources */ = { isa = PBXGroup; children = ( + DA6061CE15F20C0900097266 /* book.png */, + DA6061CF15F20C0900097266 /* book@2x.png */, + DA6061D015F20C0900097266 /* page-deblock.png */, + DA6061D115F20C0900097266 /* page-deblock@2x.png */, + DA6061D215F20C0900097266 /* page-gorillas.png */, + DA6061D315F20C0900097266 /* page-gorillas@2x.png */, + DA350A0215F11F9400C14A8E /* pull-down.png */, + DA350A0315F11F9400C14A8E /* pull-down@2x.png */, + DA350A0415F11F9400C14A8E /* pull-up.png */, + DA350A0515F11F9400C14A8E /* pull-up@2x.png */, DA55873E15E81D9E00860B4F /* iTunesArtwork-Rounded-73.png */, DA5587F115E8418200860B4F /* iTunesArtwork-Rounded-73@2x.png */, DA5587F315E8418200860B4F /* iTunesArtwork-Rounded.png */, @@ -4628,6 +4668,16 @@ DA55888915E8C0BA00860B4F /* google_plus_sign_in@2x.png in Resources */, DA55888A15E8C0BA00860B4F /* google_plus_sign_in_wide.png in Resources */, DA55888B15E8C0BA00860B4F /* google_plus_sign_in_wide@2x.png in Resources */, + DA350A0615F11F9400C14A8E /* pull-down.png in Resources */, + DA350A0715F11F9400C14A8E /* pull-down@2x.png in Resources */, + DA350A0815F11F9400C14A8E /* pull-up.png in Resources */, + DA350A0915F11F9400C14A8E /* pull-up@2x.png in Resources */, + DA6061D415F20C0900097266 /* book.png in Resources */, + DA6061D515F20C0900097266 /* book@2x.png in Resources */, + DA6061D615F20C0900097266 /* page-deblock.png in Resources */, + DA6061D715F20C0900097266 /* page-deblock@2x.png in Resources */, + DA6061D815F20C0900097266 /* page-gorillas.png in Resources */, + DA6061D915F20C0900097266 /* page-gorillas@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4776,6 +4826,8 @@ DAA096FF15E0C59B00912D63 /* MPElementGeneratedEntity.m in Sources */, DAA0970215E0C59B00912D63 /* MPElementStoredEntity.m in Sources */, DAA0970515E0C59B00912D63 /* MPUserEntity.m in Sources */, + 93D39BCE5F69D8EBE7E9F6EC /* MPAppViewController.m in Sources */, + 93D399B873AF89808151D2F5 /* MPAppsViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MasterPassword/MPAppDelegate_Key.m b/MasterPassword/MPAppDelegate_Key.m index deda78d5..445c2c9c 100644 --- a/MasterPassword/MPAppDelegate_Key.m +++ b/MasterPassword/MPAppDelegate_Key.m @@ -64,9 +64,6 @@ static NSDictionary *keyQuery(MPUserEntity *user) { inf(@"Removed key from keychain for: %@", user.userID); [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self]; -#ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointForgetSavedKey]; -#endif } } } @@ -130,9 +127,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { if (password) { inf(@"Login failed for: %@", user.userID); -#ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPCheckpointSignInFailed]; -#endif [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed attributes:nil]; } @@ -163,11 +158,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) { [[MPAppDelegate_Shared get] saveContext]; [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self]; -#ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPCheckpointSignedIn]; -#endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignedIn - attributes:nil]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignedIn attributes:nil]; return YES; } diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m index 38bd604b..060bb31d 100644 --- a/MasterPassword/MPAppDelegate_Store.m +++ b/MasterPassword/MPAppDelegate_Store.m @@ -136,9 +136,7 @@ iCloudEnabled = manager.iCloudEnabled; inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO"); -#ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled]; -#endif [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud attributes:@{ @"enabled": iCloudEnabled? @"YES": @"NO" }]; @@ -151,9 +149,12 @@ err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error); -#ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:PearlString(@"MPCheckpointMPErrorUbiquity_%d", cause)]; -#endif + [TestFlight passCheckpoint:PearlString(MPCheckpointMPErrorUbiquity @"_%d", cause)]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointMPErrorUbiquity attributes:@{ + @"cause": @(cause), + @"error.domain": error.domain, + @"error.code": @(error.code) + }]; switch (cause) { case UbiquityStoreManagerErrorCauseDeleteStore: @@ -167,16 +168,14 @@ if (error.code == NSMigrationMissingSourceModelError) { wrn(@"Resetting the local store."); -#ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible]; -#endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible attributes:nil]; + [TestFlight passCheckpoint:MPCheckpointLocalStoreReset]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreReset attributes:nil]; manager.hardResetEnabled = YES; [manager hardResetLocalStorage]; Throw(@"Local store was reset, application must be restarted to use it."); } else - // Try again. + // Try again. [[self storeManager] persistentStoreCoordinator]; } case UbiquityStoreManagerErrorCauseOpenCloudStore: { @@ -185,15 +184,13 @@ if (error.code == NSMigrationMissingSourceModelError) { wrn(@"Resetting the iCloud store."); -#ifdef TESTFLIGHT_SDK_VERSION - [TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible]; -#endif - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible attributes:nil]; + [TestFlight passCheckpoint:MPCheckpointCloudStoreReset]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreReset attributes:nil]; manager.hardResetEnabled = YES; [manager hardResetCloudStorage]; break; } else - // Try again. + // Try again. [[self storeManager] persistentStoreCoordinator]; } } @@ -419,9 +416,7 @@ [self saveContext]; success = YES; inf(@"Import completed successfully."); -#ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPCheckpointSitesImported]; -#endif [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil]; return MPImportResultSuccess; @@ -484,9 +479,7 @@ ? content: @""]; } -#ifdef TESTFLIGHT_SDK_VERSION [TestFlight passCheckpoint:MPCheckpointSitesExported]; -#endif [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesExported attributes:nil]; return export; diff --git a/MasterPassword/MPTypes.h b/MasterPassword/MPTypes.h index a008af51..d705be30 100644 --- a/MasterPassword/MPTypes.h +++ b/MasterPassword/MPTypes.h @@ -40,7 +40,6 @@ typedef enum { MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate, } MPElementType; -#define MPCheckpointAction @"MPCheckpointAction" #define MPCheckpointHelpChapter @"MPCheckpointHelpChapter" #define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard" #define MPCheckpointCopyLoginNameToPasteboard @"MPCheckpointCopyLoginNameToPasteboard" @@ -48,21 +47,13 @@ typedef enum { #define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter" #define MPCheckpointEditPassword @"MPCheckpointEditPassword" #define MPCheckpointEditLoginName @"MPCheckpointEditLoginName" -#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert" -#define MPCheckpointCloseOutdatedAlert @"MPCheckpointCloseOutdatedAlert" #define MPCheckpointUseType @"MPCheckpointUseType" #define MPCheckpointDeleteElement @"MPCheckpointDeleteElement" -#define MPCheckpointCancelSearch @"MPCheckpointCancelSearch" -#define MPCheckpointExternalLink @"MPCheckpointExternalLink" -#define MPCheckpointLaunched @"MPCheckpointLaunched" -#define MPCheckpointActivated @"MPCheckpointActivated" -#define MPCheckpointDeactivated @"MPCheckpointDeactivated" -#define MPCheckpointTerminated @"MPCheckpointTerminated" #define MPCheckpointShowGuide @"MPCheckpointShowGuide" -#define MPCheckpointForgetSavedKey @"MPCheckpointForgetSavedKey" #define MPCheckpointChangeMP @"MPCheckpointChangeMP" -#define MPCheckpointLocalStoreIncompatible @"MPCheckpointLocalStoreIncompatible" -#define MPCheckpointCloudStoreIncompatible @"MPCheckpointCloudStoreIncompatible" +#define MPCheckpointMPErrorUbiquity @"MPCheckpointMPErrorUbiquity" +#define MPCheckpointLocalStoreReset @"MPCheckpointLocalStoreReset" +#define MPCheckpointCloudStoreReset @"MPCheckpointCloudStoreReset" #define MPCheckpointSignInFailed @"MPCheckpointSignInFailed" #define MPCheckpointSignedIn @"MPCheckpointSignedIn" #define MPCheckpointConfig @"MPCheckpointConfig" @@ -72,6 +63,10 @@ typedef enum { #define MPCheckpointSitesImported @"MPCheckpointSitesImported" #define MPCheckpointSitesExported @"MPCheckpointSitesExported" #define MPCheckpointExplicitMigration @"MPCheckpointExplicitMigration" +#define MPCheckpointReview @"MPCheckpointReview" +#define MPCheckpointApps @"MPCheckpointApps" +#define MPCheckpointAppGorillas @"MPCheckpointAppGorillas" +#define MPCheckpointAppDeBlock @"MPCheckpointAppDeBlock" #define MPNotificationSignedIn @"MPNotificationKeySet" #define MPNotificationSignedOut @"MPNotificationKeyUnset" diff --git a/MasterPassword/iOS/MPAppDelegate.m b/MasterPassword/iOS/MPAppDelegate.m index bc80bac5..b7364a85 100644 --- a/MasterPassword/iOS/MPAppDelegate.m +++ b/MasterPassword/iOS/MPAppDelegate.m @@ -215,8 +215,6 @@ [super application:application didFinishLaunchingWithOptions:launchOptions]; inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]); - [TestFlight passCheckpoint:MPCheckpointLaunched]; - [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLaunched]; return YES; } @@ -341,8 +339,6 @@ inf(@"Re-activated"); [[MPAppDelegate get] checkConfig]; - [TestFlight passCheckpoint:MPCheckpointActivated]; - if (FBSession.activeSession.state == FBSessionStateCreatedOpening) // An old Facebook Login session that wasn't finished. Clean it up. [FBSession.activeSession close]; @@ -370,8 +366,6 @@ [self saveContext]; - [TestFlight passCheckpoint:MPCheckpointTerminated]; - [[LocalyticsSession sharedLocalyticsSession] close]; [[LocalyticsSession sharedLocalyticsSession] upload]; @@ -387,8 +381,6 @@ if (![[MPiOSConfig get].rememberLogin boolValue]) [self signOutAnimated:NO]; - - [TestFlight passCheckpoint:MPCheckpointDeactivated]; } #pragma mark - Behavior @@ -456,11 +448,20 @@ [self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self]; [TestFlight passCheckpoint:MPCheckpointShowGuide]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointShowGuide attributes:nil]; } - (void)showFeedback { - [self showFeedbackWithLogs:YES forVC:nil]; + [self showFeedbackWithLogs:NO forVC:nil]; +} + +- (void)showReview { + + [TestFlight passCheckpoint:MPCheckpointReview]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointReview attributes:nil]; + + [super showReview]; } - (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController { diff --git a/MasterPassword/iOS/MPAppViewController.h b/MasterPassword/iOS/MPAppViewController.h new file mode 100644 index 00000000..e319aa42 --- /dev/null +++ b/MasterPassword/iOS/MPAppViewController.h @@ -0,0 +1,26 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAppViewController +// +// Created by Maarten Billemont on 2012-08-31. +// Copyright 2012 lhunath (Maarten Billemont). All rights reserved. +// + +#import + + +@interface MPAppViewController : UIViewController + +- (IBAction)gorillas:(UIButton *)sender; +- (IBAction)deblock:(UIButton *)sender; + +@end diff --git a/MasterPassword/iOS/MPAppViewController.m b/MasterPassword/iOS/MPAppViewController.m new file mode 100644 index 00000000..74d163f7 --- /dev/null +++ b/MasterPassword/iOS/MPAppViewController.m @@ -0,0 +1,43 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAppViewController +// +// Created by Maarten Billemont on 2012-08-31. +// Copyright 2012 lhunath (Maarten Billemont). All rights reserved. +// + +#import "MPAppViewController.h" +#import "LocalyticsSession.h" + + +@implementation MPAppViewController { + +} + + +- (IBAction)gorillas:(UIButton *)sender { + + [TestFlight passCheckpoint:MPCheckpointAppGorillas]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointAppGorillas attributes:nil]; + + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://itunes.apple.com/app/lyndir/gorillas/id302275459?mt=8"]]; +} + +- (IBAction)deblock:(UIButton *)sender { + + [TestFlight passCheckpoint:MPCheckpointAppDeBlock]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointAppDeBlock attributes:nil]; + + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://itunes.apple.com/app/lyndir/deblock/id325058485?mt=8"]]; +} + +@end diff --git a/MasterPassword/iOS/MPAppsViewController.h b/MasterPassword/iOS/MPAppsViewController.h new file mode 100644 index 00000000..4d6bfb1e --- /dev/null +++ b/MasterPassword/iOS/MPAppsViewController.h @@ -0,0 +1,27 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAppsViewController +// +// Created by Maarten Billemont on 2012-08-31. +// Copyright 2012 lhunath (Maarten Billemont). All rights reserved. +// + +#import + + +@interface MPAppsViewController : UIViewController + +@property (weak, nonatomic) IBOutlet UIImageView *pagePositionView; + +- (IBAction)exit; + +@end diff --git a/MasterPassword/iOS/MPAppsViewController.m b/MasterPassword/iOS/MPAppsViewController.m new file mode 100644 index 00000000..e9850ecd --- /dev/null +++ b/MasterPassword/iOS/MPAppsViewController.m @@ -0,0 +1,115 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAppsViewController +// +// Created by Maarten Billemont on 2012-08-31. +// Copyright 2012 lhunath (Maarten Billemont). All rights reserved. +// + +#import "MPAppsViewController.h" +#import "LocalyticsSession.h" + + +@interface MPAppsViewController () + +@property (nonatomic, strong) NSMutableArray *pageVCs; +@property (nonatomic, strong) UIPageViewController *pageViewController; + + +@end + +@implementation MPAppsViewController { + +} +@synthesize pagePositionView = _pagePositionView; +@synthesize pageVCs = _pageVCs; +@synthesize pageViewController = _pageViewController; + + +- (void)viewDidLoad { + + self.pageVCs = [NSMutableArray array]; + UIViewController *vc; + @try { + for (NSUInteger p = 0; + (vc = [self.storyboard instantiateViewControllerWithIdentifier:PearlString(@"MPAppViewController_%u", p)]); + ++p) + [self.pageVCs addObject:vc]; + } + @catch (NSException *e) { + if (![e.name isEqualToString:NSInvalidArgumentException]) + [e raise]; + } + + self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl + navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal + options:nil]; + self.pageViewController.dataSource = self; + self.pageViewController.delegate = self; + [self addChildViewController:self.pageViewController]; + [self.view addSubview:self.pageViewController.view]; + self.pageViewController.view.frame = self.pagePositionView.frame; + [self.pagePositionView removeFromSuperview]; + [self.pageViewController didMoveToParentViewController:self]; + + [self.pageViewController setViewControllers:@[[self.pageVCs objectAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward + animated:NO completion:nil]; + + [super viewDidLoad]; +} + +- (void)viewWillAppear:(BOOL)animated { + + [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide]; + + [TestFlight passCheckpoint:MPCheckpointApps]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointApps attributes:nil]; + + [super viewWillAppear:animated]; +} + +- (void)viewWillDisappear:(BOOL)animated { + + [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]; + + [super viewWillDisappear:animated]; +} + + +- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController + viewControllerBeforeViewController:(UIViewController *)viewController { + + NSUInteger vcIndex = [self.pageVCs indexOfObject:viewController]; + return [self.pageVCs objectAtIndex:(vcIndex + [self.pageVCs count] - 1) % self.pageVCs.count]; +} + +- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController + viewControllerAfterViewController:(UIViewController *)viewController { + + NSUInteger vcIndex = [self.pageVCs indexOfObject:viewController]; + UIPageViewController *vc = [self.pageVCs objectAtIndex:(vcIndex + 1) % self.pageVCs.count]; + + return vc; +} + +- (void)viewDidUnload { + + [self setPagePositionView:nil]; + [super viewDidUnload]; +} + +- (IBAction)exit { + + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/MasterPassword/iOS/MPMainViewController.h b/MasterPassword/iOS/MPMainViewController.h index ea00c524..8c2bfaa8 100644 --- a/MasterPassword/iOS/MPMainViewController.h +++ b/MasterPassword/iOS/MPMainViewController.h @@ -46,6 +46,8 @@ @property (weak, nonatomic) IBOutlet UIView *outdatedAlertContainer; @property (weak, nonatomic) IBOutlet UIImageView *outdatedAlertBack; @property (weak, nonatomic) IBOutlet UIButton *outdatedAlertCloseButton; +@property (weak, nonatomic) IBOutlet UIImageView *pullUpView; +@property (weak, nonatomic) IBOutlet UIImageView *pullDownView; @property (copy, nonatomic) void (^contentTipCleanup)(BOOL finished); @@ -67,5 +69,7 @@ - (void)toggleHelpAnimated:(BOOL)animated; - (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated; - (void)setHelpChapter:(NSString *)chapter; +- (IBAction)panHelpDown:(UIPanGestureRecognizer *)sender; +- (IBAction)panHelpUp:(UIPanGestureRecognizer *)sender; @end diff --git a/MasterPassword/iOS/MPMainViewController.m b/MasterPassword/iOS/MPMainViewController.m index ba13e699..8aee3b00 100644 --- a/MasterPassword/iOS/MPMainViewController.m +++ b/MasterPassword/iOS/MPMainViewController.m @@ -46,6 +46,8 @@ @synthesize outdatedAlertContainer = _outdatedAlertContainer; @synthesize outdatedAlertBack = _outdatedAlertBack; @synthesize outdatedAlertCloseButton = _outdatedAlertCloseButton; +@synthesize pullUpView = _pullUpView; +@synthesize pullDownView = _pullDownView; @synthesize contentField = _contentField; @synthesize contentTipCleanup = _contentTipCleanup, toolTipCleanup = _toolTipCleanup; @@ -241,6 +243,8 @@ [self setOutdatedAlertContainer:nil]; [self setOutdatedAlertCloseButton:nil]; [self setOutdatedAlertBack:nil]; + [self setPullUpView:nil]; + [self setPullDownView:nil]; [super viewDidUnload]; } @@ -324,13 +328,15 @@ return; } + self.pullUpView.hidden = ![[MPiOSConfig get].helpHidden boolValue]; + self.pullDownView.hidden = [[MPiOSConfig get].helpHidden boolValue]; + if ([[MPiOSConfig get].helpHidden boolValue]) { self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44 /* search bar */); - self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, - self.view.bounds.size.height + 20 /* view moves up a bit when search appears. */); + self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height - 20); } else { self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 225); - self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 266); + self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 246); } } @@ -366,6 +372,7 @@ - (void)setHelpChapter:(NSString *)chapter { [TestFlight passCheckpoint:PearlString(MPCheckpointHelpChapter @"_%@", chapter)]; + [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointHelpChapter attributes:@{@"chapter": chapter}]; dispatch_async(dispatch_get_main_queue(), ^{ NSURL *url = [NSURL URLWithString:[@"#" stringByAppendingString:chapter] @@ -374,6 +381,36 @@ }); } +- (IBAction)panHelpDown:(UIPanGestureRecognizer *)sender { + + CGFloat targetY = MIN(self.view.bounds.size.height - 20, 246 + [sender translationInView:self.helpContainer].y); + BOOL hideHelp = YES; + if (targetY <= 246) { + hideHelp = NO; + targetY = 246; + } + + self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, targetY); + + if (sender.state == UIGestureRecognizerStateEnded) + [self setHelpHidden:hideHelp animated:YES]; +} + +- (IBAction)panHelpUp:(UIPanGestureRecognizer *)sender { + + CGFloat targetY = MAX(246, self.view.bounds.size.height - 20 + [sender translationInView:self.helpContainer].y); + BOOL hideHelp = NO; + if (targetY >= self.view.bounds.size.height - 20) { + hideHelp = YES; + targetY = self.view.bounds.size.height - 20 ; + } + + self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, targetY); + + if (sender.state == UIGestureRecognizerStateEnded) + [self setHelpHidden:hideHelp animated:YES]; +} + - (void)webViewDidFinishLoad:(UIWebView *)webView { NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString: @@ -655,8 +692,6 @@ if (finished) self.alertBody.text = nil; }]; - - [TestFlight passCheckpoint:MPCheckpointCloseAlert]; } - (IBAction)closeOutdatedAlert { @@ -664,8 +699,6 @@ [UIView animateWithDuration:0.3f animations:^{ self.outdatedAlertContainer.alpha = 0; }]; - - [TestFlight passCheckpoint:MPCheckpointCloseOutdatedAlert]; } - (IBAction)infoOutdatedAlert { @@ -686,26 +719,26 @@ switch (buttonIndex - [sheet firstOtherButtonIndex]) { case 0: { - inf(@"Action: Toggle Help"); - [self toggleHelpAnimated:YES]; - break; - } - case 1: { inf(@"Action: FAQ"); [self setHelpChapter:@"faq"]; [self setHelpHidden:NO animated:YES]; break; } - case 2: { + case 1: { inf(@"Action: Guide"); [[MPAppDelegate get] showGuide]; break; } - case 3: { + case 2: { inf(@"Action: Preferences"); [self performSegueWithIdentifier:@"UserProfile" sender:self]; break; } + case 3: { + inf(@"Action: Other Apps"); + [self performSegueWithIdentifier:@"OtherApps" sender:self]; + break; + } #ifdef ADHOC case 4: { inf(@"Action: Feedback via TestFlight"); @@ -732,11 +765,9 @@ break; } } - - [TestFlight passCheckpoint:MPCheckpointAction]; } cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles: - [[MPiOSConfig get].helpHidden boolValue]? @"Show Help": @"Hide Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", + @"FAQ", @"Tutorial", @"Preferences", @"Other Apps", @"Feedback", @"Sign Out", nil]; } @@ -872,8 +903,6 @@ return NO; } - [TestFlight passCheckpoint:MPCheckpointExternalLink]; - [[UIApplication sharedApplication] openURL:[request URL]]; return NO; } diff --git a/MasterPassword/iOS/MPSearchDelegate.m b/MasterPassword/iOS/MPSearchDelegate.m index 5d30c0e0..afc8dd2b 100644 --- a/MasterPassword/iOS/MPSearchDelegate.m +++ b/MasterPassword/iOS/MPSearchDelegate.m @@ -90,8 +90,6 @@ - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { - [TestFlight passCheckpoint:MPCheckpointCancelSearch]; - [self.delegate didSelectElement:nil]; } diff --git a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard index 6af7a605..57c611b6 100644 --- a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard +++ b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard @@ -1,7 +1,8 @@ - + - + + @@ -27,23 +28,23 @@ - @@ -62,21 +63,21 @@ - + @@ -1906,6 +2089,10 @@ You could use the word wall for inspiration in finding a memorable master passw + + + + @@ -1925,6 +2112,20 @@ You could use the word wall for inspiration in finding a memorable master passw + + + + + + + + + + + + + + @@ -1944,6 +2145,8 @@ You could use the word wall for inspiration in finding a memorable master passw + + @@ -1971,6 +2174,8 @@ You could use the word wall for inspiration in finding a memorable master passw + + @@ -2042,6 +2247,6 @@ You could use the word wall for inspiration in finding a memorable master passw - + \ No newline at end of file diff --git a/Resources/book.png b/Resources/book.png new file mode 100644 index 00000000..e452d3b0 Binary files /dev/null and b/Resources/book.png differ diff --git a/Resources/book@2x.png b/Resources/book@2x.png new file mode 100644 index 00000000..f41f2b63 Binary files /dev/null and b/Resources/book@2x.png differ diff --git a/Resources/page-deblock.png b/Resources/page-deblock.png new file mode 100644 index 00000000..91ab7178 Binary files /dev/null and b/Resources/page-deblock.png differ diff --git a/Resources/page-deblock@2x.png b/Resources/page-deblock@2x.png new file mode 100644 index 00000000..892bc088 Binary files /dev/null and b/Resources/page-deblock@2x.png differ diff --git a/Resources/page-gorillas.png b/Resources/page-gorillas.png new file mode 100644 index 00000000..f657ee34 Binary files /dev/null and b/Resources/page-gorillas.png differ diff --git a/Resources/page-gorillas@2x.png b/Resources/page-gorillas@2x.png new file mode 100644 index 00000000..149412dc Binary files /dev/null and b/Resources/page-gorillas@2x.png differ diff --git a/Resources/pull-down.png b/Resources/pull-down.png new file mode 100644 index 00000000..6ce7ff0d Binary files /dev/null and b/Resources/pull-down.png differ diff --git a/Resources/pull-down@2x.png b/Resources/pull-down@2x.png new file mode 100644 index 00000000..c7bcffa8 Binary files /dev/null and b/Resources/pull-down@2x.png differ diff --git a/Resources/pull-up.png b/Resources/pull-up.png new file mode 100644 index 00000000..bb084eeb Binary files /dev/null and b/Resources/pull-up.png differ diff --git a/Resources/pull-up@2x.png b/Resources/pull-up@2x.png new file mode 100644 index 00000000..9388ade5 Binary files /dev/null and b/Resources/pull-up@2x.png differ