From 41b39643631cc5482f7416d8780f821c87b62468 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sun, 28 Sep 2014 22:15:55 -0400 Subject: [PATCH] Lots of UI improvements and tips + parental gate, guide update. [IMPROVED] Emergency VC can now scroll when keyboard is up. [IMPROVED] Language of the guide + new updated screenshots. [FIXED] Size of guide cells on different devices. [IMPROVED] Don't show messages claiming login name was updated when nothing changed. [FIXED] Weird back-toggle bug when toggling site settings. [ADDED] Lots of handy tips throughout. [ADDED] Notification of new store features. [FIXED] Weird sizing issue & animation with store cells. [ADDED] Loading spinner while loading store products. [ADDED] Thanks link to store footer. [FIXED] Bought products should not respond to click, non-bought ones should. [FIXED] Fuel elapsed time counter was backward. [ADDED] Parental gate when deleting or resetting users. [UPDATED] App Icon background texture. --- .gitmodules | 3 + External/AttributedMarkdown | 1 + .../xcshareddata/MasterPassword.xccheckout | 12 + MasterPassword/ObjC/MPAppDelegate_InApp.h | 2 + MasterPassword/ObjC/MPAppDelegate_InApp.m | 10 + .../AttributedMarkdown/AttributedMarkdown.h | 13 + .../AttributedMarkdown/AttributedMarkdown.m | 13 + .../iOS/AttributedMarkdownTests/Info.plist | 24 + .../ObjC/iOS/MPEmergencyViewController.h | 1 + .../ObjC/iOS/MPEmergencyViewController.m | 3 + .../ObjC/iOS/MPGuideViewController.m | 74 +- MasterPassword/ObjC/iOS/MPPasswordCell.m | 20 +- .../ObjC/iOS/MPPasswordsViewController.h | 1 + .../ObjC/iOS/MPPasswordsViewController.m | 32 +- .../ObjC/iOS/MPPreferencesViewController.m | 1 + .../ObjC/iOS/MPStoreViewController.h | 3 + .../ObjC/iOS/MPStoreViewController.m | 71 +- .../ObjC/iOS/MPUsersViewController.h | 13 +- .../ObjC/iOS/MPUsersViewController.m | 165 +++-- MasterPassword/ObjC/iOS/MPiOSAppDelegate.m | 9 + .../project.pbxproj | 332 ++++++--- MasterPassword/ObjC/iOS/NSString+MPMarkDown.h | 25 + MasterPassword/ObjC/iOS/NSString+MPMarkDown.m | 56 ++ MasterPassword/ObjC/iOS/Storyboard.storyboard | 686 +++++++++++------- .../Resources/Media/Guide/choose_type.png | Bin 0 -> 65761 bytes .../Resources/Media/Guide/choose_type@2x.png | Bin 0 -> 139672 bytes .../Resources/Media/Guide/copy_pw.png | Bin 0 -> 56480 bytes .../Resources/Media/Guide/copy_pw@2x.png | Bin 0 -> 114247 bytes .../Resources/Media/Guide/counter.png | Bin 0 -> 65916 bytes .../Resources/Media/Guide/counter@2x.png | Bin 0 -> 139765 bytes .../Resources/Media/Guide/image-0.png | Bin 72675 -> 0 bytes .../Resources/Media/Guide/image-0@2x.png | Bin 220685 -> 0 bytes .../Resources/Media/Guide/image-1.png | Bin 76999 -> 0 bytes .../Resources/Media/Guide/image-10.png | Bin 63214 -> 0 bytes .../Resources/Media/Guide/image-10@2x.png | Bin 169615 -> 0 bytes .../Resources/Media/Guide/image-1@2x.png | Bin 196973 -> 0 bytes .../Resources/Media/Guide/image-2.png | Bin 45836 -> 0 bytes .../Resources/Media/Guide/image-2@2x.png | Bin 191938 -> 0 bytes .../Resources/Media/Guide/image-3.png | Bin 27832 -> 0 bytes .../Resources/Media/Guide/image-3@2x.png | Bin 152237 -> 0 bytes .../Resources/Media/Guide/image-4.png | Bin 34656 -> 0 bytes .../Resources/Media/Guide/image-4@2x.png | Bin 96449 -> 0 bytes .../Resources/Media/Guide/image-5.png | Bin 60075 -> 0 bytes .../Resources/Media/Guide/image-5@2x.png | Bin 166985 -> 0 bytes .../Resources/Media/Guide/image-6.png | Bin 66301 -> 0 bytes .../Resources/Media/Guide/image-6@2x.png | Bin 187036 -> 0 bytes .../Resources/Media/Guide/image-7.png | Bin 64720 -> 0 bytes .../Resources/Media/Guide/image-7@2x.png | Bin 182989 -> 0 bytes .../Resources/Media/Guide/image-8.png | Bin 67069 -> 0 bytes .../Resources/Media/Guide/image-8@2x.png | Bin 188745 -> 0 bytes .../Resources/Media/Guide/image-9.png | Bin 64550 -> 0 bytes .../Resources/Media/Guide/image-9@2x.png | Bin 180349 -> 0 bytes .../Resources/Media/Guide/initial.png | Bin 0 -> 55636 bytes .../Resources/Media/Guide/initial@2x.png | Bin 0 -> 191242 bytes .../Resources/Media/Guide/login_name.png | Bin 0 -> 67713 bytes .../Resources/Media/Guide/login_name@2x.png | Bin 0 -> 142854 bytes .../Resources/Media/Guide/login_new.png | Bin 0 -> 43064 bytes .../Resources/Media/Guide/login_new@2x.png | Bin 0 -> 117077 bytes .../Resources/Media/Guide/mpw_new.png | Bin 0 -> 70182 bytes .../Resources/Media/Guide/mpw_new@2x.png | Bin 0 -> 191028 bytes .../Resources/Media/Guide/name_new.png | Bin 0 -> 70898 bytes .../Resources/Media/Guide/name_new@2x.png | Bin 0 -> 192876 bytes .../Resources/Media/Guide/personal_pw.png | Bin 0 -> 70516 bytes .../Resources/Media/Guide/personal_pw@2x.png | Bin 0 -> 147729 bytes .../Resources/Media/Guide/settings.png | Bin 0 -> 57765 bytes .../Resources/Media/Guide/settings@2x.png | Bin 0 -> 119486 bytes .../Resources/Media/Guide/site_new.png | Bin 0 -> 45063 bytes .../Resources/Media/Guide/site_new@2x.png | Bin 0 -> 94428 bytes .../AppIcon.appiconset/Icon-60@2x.png | Bin 18214 -> 19275 bytes .../AppIcon.appiconset/Icon-60@3x.png | Bin 37527 -> 38084 bytes .../AppIcon.appiconset/Icon-76.png | Bin 8454 -> 8779 bytes .../AppIcon.appiconset/Icon-76@2x.png | Bin 28467 -> 28523 bytes .../AppIcon.appiconset/Icon-Small-40.png | Bin 2816 -> 2643 bytes .../AppIcon.appiconset/Icon-Small-40@2x.png | Bin 8885 -> 8583 bytes .../AppIcon.appiconset/Icon-Small-40@3x.png | Bin 17782 -> 18847 bytes .../AppIcon.appiconset/Icon-Small.png | Bin 1715 -> 1687 bytes .../AppIcon.appiconset/Icon-Small@2x.png | Bin 5134 -> 5689 bytes .../AppIcon.appiconset/Icon-Small@3x.png | Bin 9970 -> 9858 bytes .../Resources/Media/ios/icon.sketch/Data | Bin 353295 -> 371142 bytes .../Resources/Media/ios/icon.sketch/metadata | 2 +- .../Resources/Media/ios/icon/Icon-60.png | Bin 5673 -> 5354 bytes .../Resources/Media/ios/icon/Icon-60@2x.png | Bin 18214 -> 19275 bytes .../Resources/Media/ios/icon/Icon-60@3x.png | Bin 37527 -> 38084 bytes .../Resources/Media/ios/icon/Icon-76.png | Bin 8454 -> 8779 bytes .../Resources/Media/ios/icon/Icon-76@2x.png | Bin 28467 -> 28523 bytes .../Media/ios/icon/Icon-Small-40.png | Bin 2816 -> 2643 bytes .../Media/ios/icon/Icon-Small-40@2x.png | Bin 8885 -> 8583 bytes .../Media/ios/icon/Icon-Small-40@3x.png | Bin 17782 -> 18847 bytes .../Resources/Media/ios/icon/Icon-Small.png | Bin 1715 -> 1687 bytes .../Media/ios/icon/Icon-Small@2x.png | Bin 5134 -> 5689 bytes .../Media/ios/icon/Icon-Small@3x.png | Bin 9970 -> 9858 bytes .../Media/ios/icon/iTunesArtwork.png | Bin 312844 -> 251321 bytes .../Media/ios/icon/iTunesArtwork@2x.png | Bin 1116245 -> 912171 bytes .../Resources/Media/ios/launch.sketch/Data | Bin 297465 -> 297779 bytes .../Media/ios/launch.sketch/metadata | 2 +- .../Resources/Media/thumb_ios_integration.png | Bin 0 -> 42782 bytes .../Media/thumb_ios_integration@2x.png | Bin 0 -> 135807 bytes .../Media/thumb_ios_integration@3x.png | Bin 0 -> 348916 bytes .../Resources/Media/thumb_touch_id.png | Bin 0 -> 33699 bytes .../Resources/Media/thumb_touch_id@2x.png | Bin 0 -> 98270 bytes .../Resources/Media/thumb_touch_id@3x.png | Bin 0 -> 243472 bytes MasterPassword/Resources/Raw/Store_Thumb.psd | Bin 7320100 -> 23124082 bytes 102 files changed, 1108 insertions(+), 466 deletions(-) create mode 160000 External/AttributedMarkdown create mode 100644 MasterPassword/ObjC/iOS/AttributedMarkdown/AttributedMarkdown.h create mode 100644 MasterPassword/ObjC/iOS/AttributedMarkdown/AttributedMarkdown.m create mode 100644 MasterPassword/ObjC/iOS/AttributedMarkdownTests/Info.plist create mode 100644 MasterPassword/ObjC/iOS/NSString+MPMarkDown.h create mode 100644 MasterPassword/ObjC/iOS/NSString+MPMarkDown.m create mode 100644 MasterPassword/Resources/Media/Guide/choose_type.png create mode 100644 MasterPassword/Resources/Media/Guide/choose_type@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/copy_pw.png create mode 100644 MasterPassword/Resources/Media/Guide/copy_pw@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/counter.png create mode 100644 MasterPassword/Resources/Media/Guide/counter@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-0.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-0@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-1.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-10.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-10@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-1@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-2.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-2@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-3.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-3@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-4.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-4@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-5.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-5@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-6.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-6@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-7.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-7@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-8.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-8@2x.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-9.png delete mode 100644 MasterPassword/Resources/Media/Guide/image-9@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/initial.png create mode 100644 MasterPassword/Resources/Media/Guide/initial@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/login_name.png create mode 100644 MasterPassword/Resources/Media/Guide/login_name@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/login_new.png create mode 100644 MasterPassword/Resources/Media/Guide/login_new@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/mpw_new.png create mode 100644 MasterPassword/Resources/Media/Guide/mpw_new@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/name_new.png create mode 100644 MasterPassword/Resources/Media/Guide/name_new@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/personal_pw.png create mode 100644 MasterPassword/Resources/Media/Guide/personal_pw@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/settings.png create mode 100644 MasterPassword/Resources/Media/Guide/settings@2x.png create mode 100644 MasterPassword/Resources/Media/Guide/site_new.png create mode 100644 MasterPassword/Resources/Media/Guide/site_new@2x.png create mode 100644 MasterPassword/Resources/Media/thumb_ios_integration.png create mode 100644 MasterPassword/Resources/Media/thumb_ios_integration@2x.png create mode 100644 MasterPassword/Resources/Media/thumb_ios_integration@3x.png create mode 100644 MasterPassword/Resources/Media/thumb_touch_id.png create mode 100644 MasterPassword/Resources/Media/thumb_touch_id@2x.png create mode 100644 MasterPassword/Resources/Media/thumb_touch_id@3x.png diff --git a/.gitmodules b/.gitmodules index ee284834..fb1fda92 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "External/KCOrderedAccessorFix"] path = External/KCOrderedAccessorFix url = https://github.com/CFKevinRef/KCOrderedAccessorFix.git +[submodule "External/AttributedMarkdown"] + path = External/AttributedMarkdown + url = https://github.com/dreamwieber/AttributedMarkdown.git diff --git a/External/AttributedMarkdown b/External/AttributedMarkdown new file mode 160000 index 00000000..d598fb4f --- /dev/null +++ b/External/AttributedMarkdown @@ -0,0 +1 @@ +Subproject commit d598fb4f5e29f5aaa66e7e880a9857019865881b diff --git a/MasterPassword.xcworkspace/xcshareddata/MasterPassword.xccheckout b/MasterPassword.xcworkspace/xcshareddata/MasterPassword.xccheckout index cbb4fb9f..f775dc76 100644 --- a/MasterPassword.xcworkspace/xcshareddata/MasterPassword.xccheckout +++ b/MasterPassword.xcworkspace/xcshareddata/MasterPassword.xccheckout @@ -22,6 +22,8 @@ https://github.com/CFKevinRef/KCOrderedAccessorFix.git 3E67FB08419C920516AAC3B00DAAF23073B8CF77 git://github.com/lhunath/RHStatusItemView.git + 3ED8592497DB6A564366943C9AAD5A46341B5076 + https://github.com/dreamwieber/AttributedMarkdown.git 4DDCFFD91B41F00326AD14553BD66CFD366ABD91 ssh://github.com/Lyndir/Pearl.git 8A15A8EA0B3D0B497C4883425BC74DF995224BB3 @@ -47,6 +49,8 @@ ../External/KCOrderedAccessorFix 3E67FB08419C920516AAC3B00DAAF23073B8CF77 ../External/RHStatusItemView + 3ED8592497DB6A564366943C9AAD5A46341B5076 + ../External/AttributedMarkdown/ 4DDCFFD91B41F00326AD14553BD66CFD366ABD91 ../External/Pearl 8A15A8EA0B3D0B497C4883425BC74DF995224BB3 @@ -72,6 +76,14 @@ IDESourceControlWCCName + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 3ED8592497DB6A564366943C9AAD5A46341B5076 + IDESourceControlWCCName + AttributedMarkdown + IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git diff --git a/MasterPassword/ObjC/MPAppDelegate_InApp.h b/MasterPassword/ObjC/MPAppDelegate_InApp.h index 64661cc0..2bf85874 100644 --- a/MasterPassword/ObjC/MPAppDelegate_InApp.h +++ b/MasterPassword/ObjC/MPAppDelegate_InApp.h @@ -11,6 +11,8 @@ #define MPProductGenerateLogins @"com.lyndir.masterpassword.products.generatelogins" #define MPProductGenerateAnswers @"com.lyndir.masterpassword.products.generateanswers" +#define MPProductOSIntegration @"com.lyndir.masterpassword.products.osintegration" +#define MPProductTouchID @"com.lyndir.masterpassword.products.touchid" #define MPProductFuel @"com.lyndir.masterpassword.products.fuel" #define MP_FUEL_HOURLY_RATE 30.f /* Tier 1 purchases/h ~> USD/h */ diff --git a/MasterPassword/ObjC/MPAppDelegate_InApp.m b/MasterPassword/ObjC/MPAppDelegate_InApp.m index 4188761d..bcb1c180 100644 --- a/MasterPassword/ObjC/MPAppDelegate_InApp.m +++ b/MasterPassword/ObjC/MPAppDelegate_InApp.m @@ -81,6 +81,16 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve - (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity { +#if TARGET_OS_IPHONE + if (![[MPAppDelegate_Shared get] canMakePayments]) { + [PearlAlert showAlertWithTitle:@"Store Not Set Up" message: + @"Try logging using the App Store or from Settings." + viewStyle:UIAlertViewStyleDefault initAlert:nil + tappedButtonBlock:nil cancelTitle:@"Thanks" otherTitles:nil]; + return; + } +#endif + for (SKProduct *product in self.products) if ([product.productIdentifier isEqualToString:productIdentifier]) { SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; diff --git a/MasterPassword/ObjC/iOS/AttributedMarkdown/AttributedMarkdown.h b/MasterPassword/ObjC/iOS/AttributedMarkdown/AttributedMarkdown.h new file mode 100644 index 00000000..4851313f --- /dev/null +++ b/MasterPassword/ObjC/iOS/AttributedMarkdown/AttributedMarkdown.h @@ -0,0 +1,13 @@ +// +// AttributedMarkdown.h +// AttributedMarkdown +// +// Created by Maarten Billemont on 2014-09-28. +// Copyright (c) 2014 Lyndir. All rights reserved. +// + +#import + +@interface AttributedMarkdown : NSObject + +@end diff --git a/MasterPassword/ObjC/iOS/AttributedMarkdown/AttributedMarkdown.m b/MasterPassword/ObjC/iOS/AttributedMarkdown/AttributedMarkdown.m new file mode 100644 index 00000000..62a52d53 --- /dev/null +++ b/MasterPassword/ObjC/iOS/AttributedMarkdown/AttributedMarkdown.m @@ -0,0 +1,13 @@ +// +// AttributedMarkdown.m +// AttributedMarkdown +// +// Created by Maarten Billemont on 2014-09-28. +// Copyright (c) 2014 Lyndir. All rights reserved. +// + +#import "AttributedMarkdown.h" + +@implementation AttributedMarkdown + +@end diff --git a/MasterPassword/ObjC/iOS/AttributedMarkdownTests/Info.plist b/MasterPassword/ObjC/iOS/AttributedMarkdownTests/Info.plist new file mode 100644 index 00000000..c87bb631 --- /dev/null +++ b/MasterPassword/ObjC/iOS/AttributedMarkdownTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.lyndir.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/MasterPassword/ObjC/iOS/MPEmergencyViewController.h b/MasterPassword/ObjC/iOS/MPEmergencyViewController.h index 955f7e3e..9eac4c9c 100644 --- a/MasterPassword/ObjC/iOS/MPEmergencyViewController.h +++ b/MasterPassword/ObjC/iOS/MPEmergencyViewController.h @@ -19,6 +19,7 @@ @interface MPEmergencyViewController : UIViewController +@property(weak, nonatomic) IBOutlet UIScrollView *scrollView; @property(weak, nonatomic) IBOutlet UIView *dialogView; @property(weak, nonatomic) IBOutlet UIView *containerView; @property(weak, nonatomic) IBOutlet UITextField *userNameField; diff --git a/MasterPassword/ObjC/iOS/MPEmergencyViewController.m b/MasterPassword/ObjC/iOS/MPEmergencyViewController.m index 8d4f4c9f..b54718e5 100644 --- a/MasterPassword/ObjC/iOS/MPEmergencyViewController.m +++ b/MasterPassword/ObjC/iOS/MPEmergencyViewController.m @@ -45,6 +45,8 @@ ^(MPEmergencyViewController *self, NSNotification *note) { [self performSegueWithIdentifier:@"unwind-popover" sender:self]; } ); + + [self.scrollView automaticallyAdjustInsetsForKeyboard]; } - (void)viewDidDisappear:(BOOL)animated { @@ -52,6 +54,7 @@ [super viewDidDisappear:animated]; PearlRemoveNotificationObservers(); + PearlRemoveNotificationObserversFrom( self.scrollView ); [self reset]; } diff --git a/MasterPassword/ObjC/iOS/MPGuideViewController.m b/MasterPassword/ObjC/iOS/MPGuideViewController.m index 57e07676..0119c253 100644 --- a/MasterPassword/ObjC/iOS/MPGuideViewController.m +++ b/MasterPassword/ObjC/iOS/MPGuideViewController.m @@ -7,6 +7,8 @@ // #import "MPGuideViewController.h" +#import "markdown_lib.h" +#import "NSString+MPMarkDown.h" @interface MPGuideStep : NSObject @@ -37,28 +39,50 @@ [super viewDidLoad]; self.steps = @[ - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-0"] caption: - @"To begin, tap the \"New User\" icon and add yourself as a user to the application."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-1"] caption: - @"Enter your full name. Double-check that you have spelled your name correctly and capitalized it appropriately. Your passwords will depend on it."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-2"] caption: - @"Choose a master password: Use something new and long. A short sentence is ideal.\nDO NOT FORGET THIS ONE PASSWORD."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-3"] caption: - @"After logging in, you'll see an empty screen with a search box.\nTap the search box to begin adding sites."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-4"] caption: - @"To add a site, just enter its name fully and tap the result. Names can be anything, but we recommend using a site's bare domain name."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-5"] caption: - @"Your sites are easy to find and sorted by recency.\nTap any site to copy its password.\nYou can now switch and paste it in another app."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-6"] caption: - @"The user icon lets you save your site's login.\nThis is useful if you find it hard to remember the user name for this site."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-7"] caption: + [MPGuideStep stepWithImage:[UIImage imageNamed:@"initial"] caption: + @"To begin, tap the *New User* icon and add yourself as a user to the application."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"name_new"] caption: + @"Enter your full name. \n" + @"**Double-check** that you have spelled your name correctly and capitalized it appropriately. \n" + @"Your passwords will depend on it."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"mpw_new"] caption: + @"Choose a master password: Make it *new* and *long*. \n" + @"A short phrase makes a great password. \n" + @"**DO NOT FORGET THIS ONE PASSWORD**."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"login_new"] caption: + @"After logging in, you'll see an empty screen with a search box. \n" + @"Tap the search box to begin adding sites."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"site_new"] caption: + @"To add a site, just enter its name and tap the result. \n" + @"*We recommend* always using a site's **bare** domain name: eg. *apple.com*. \n" + @"(NOT *www.*apple.com or *store.*apple.com)"], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"copy_pw"] caption: + @"Tap any site to copy its password. \n" + @"The first time, change your site's old password into this new one."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"settings"] caption: @"To make changes to the site password, tap the settings icon or swipe left to reveal extra buttons."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-8"] caption: - @"If you ever need a new password for the site, just tap the plus icon to increment its counter.\nYou can hold down to reset it back to 1."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-9"] caption: - @"Use the list icon to upgrade or downgrade your password's complexity.\nSome sites won't let you use complex passwords."], - [MPGuideStep stepWithImage:[UIImage imageNamed:@"image-10"] caption: - @"If you have a password that you cannot change, you can save it as a Personal password. Device Private means the site will not be backed up."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"login_name"] caption: + @"You can save the login name for the site. \n" + @"This is useful if you find it hard to remember your user name for this site."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"counter"] caption: + @"If you ever need a new password for the site, just tap the plus icon to increment its counter. \n" + @"You can hold down to reset it back to 1."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"choose_type"] caption: + @"Use the list icon to upgrade or downgrade your password's complexity. \n" + @"Some sites won't let you use complex passwords."], + + [MPGuideStep stepWithImage:[UIImage imageNamed:@"personal_pw"] caption: + @"If you have a password that you cannot change, you can save it as a *personal* password. " + @"*Device private* means the site will not be backed up."], ]; } @@ -68,9 +92,10 @@ [self.pageControl observeKeyPath:@"currentPage" withBlock:^(id from, id to, NSKeyValueChange cause, UIPageControl *pageControl) { - MPGuideStep *activeStep = self.steps[pageControl.currentPage]; - self.captionLabel.text = activeStep.caption; - }]; + MPGuideStep *activeStep = self.steps[pageControl.currentPage]; + self.captionLabel.attributedText = + [activeStep.caption attributedMarkdownStringWithFontSize:self.captionLabel.font.pointSize]; + }]; [self.collectionView setContentOffset:CGPointZero]; self.pageControl.currentPage = 0; @@ -117,6 +142,7 @@ MPGuideStepCell *cell = [MPGuideStepCell dequeueCellFromCollectionView:collectionView indexPath:indexPath]; cell.imageView.image = ((MPGuideStep *)self.steps[indexPath.item]).image; + cell.contentView.frame = cell.bounds; return cell; } diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.m b/MasterPassword/ObjC/iOS/MPPasswordCell.m index 64f4ae0f..191ac8dc 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.m +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.m @@ -226,12 +226,15 @@ else if (textField == self.loginNameField && ((site.loginGenerated && ![text length]) || (!site.loginGenerated && ![text isEqualToString:site.loginName]))) { - site.loginName = text; - site.loginGenerated = NO; - if ([text length]) - [PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Saved" dismissAfter:2]; - else - [PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Cleared" dismissAfter:2]; + if (site.loginGenerated || !([site.loginName isEqualToString:text] || (!text && !site.loginName))) { + site.loginGenerated = NO; + site.loginName = text; + + if ([text length]) + [PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Saved" dismissAfter:2]; + else + [PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Cleared" dismissAfter:2]; + } } [context saveToStore]; @@ -310,8 +313,6 @@ [self setMode:MPPasswordCellModePassword animated:YES]; break; } - - [self updateAnimated:YES]; } - (IBAction)doUpgrade:(UIButton *)sender { @@ -499,6 +500,9 @@ [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { MPSiteEntity *site = [self siteInContext:context]; MPKey *key = [MPiOSAppDelegate get].key; + if (!key) + return; + NSString *password, *loginName = [site resolveLoginUsingKey:key]; if (self.transientSite) password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType: diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.h b/MasterPassword/ObjC/iOS/MPPasswordsViewController.h index e3f51ac7..527a2d74 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.h +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.h @@ -27,6 +27,7 @@ @property(strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsToBottomConstraint; @property(strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint; @property(strong, nonatomic) IBOutlet NSLayoutConstraint *popdownToTopConstraint; +@property(strong, nonatomic) IBOutlet UIView *badNameTipContainer; @property(strong, nonatomic) IBOutlet UIView *popdownView; @property(strong, nonatomic) IBOutlet UIView *popdownContainer; diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m index a5bd2e3c..7a31222c 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m @@ -24,6 +24,10 @@ #import "MPPasswordCell.h" #import "MPAnswersViewController.h" +typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) { + MPPasswordsBadNameTip = 1 << 0, +}; + @interface MPPasswordsViewController() @property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar; @@ -39,6 +43,7 @@ __weak UIViewController *_popdownVC; BOOL _showTransientItem; NSUInteger _transientItem; + NSCharacterSet *_siteNameAcceptableCharactersSet; } #pragma mark - Life @@ -47,6 +52,11 @@ [super viewDidLoad]; + NSMutableCharacterSet *siteNameAcceptableCharactersSet = [[NSCharacterSet alphanumericCharacterSet] mutableCopy]; + [siteNameAcceptableCharactersSet formIntersectionWithCharacterSet:[[NSCharacterSet uppercaseLetterCharacterSet] invertedSet]]; + [siteNameAcceptableCharactersSet addCharactersInString:@"@.-+~&_;:/"]; + _siteNameAcceptableCharactersSet = siteNameAcceptableCharactersSet; + _backgroundColor = self.passwordCollectionView.backgroundColor; _darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f]; _transientItem = NSNotFound; @@ -232,12 +242,32 @@ - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { - if (searchBar == self.passwordsSearchBar) + if (searchBar == self.passwordsSearchBar) { + if ([self.query length] && [[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length]) + [self showTips:MPPasswordsBadNameTip]; + [self updatePasswords]; + } } #pragma mark - Private +- (void)showTips:(MPPasswordsTips)showTips { + + [UIView animateWithDuration:0.3f animations:^{ + if (showTips & MPPasswordsBadNameTip) + self.badNameTipContainer.alpha = 1; + } completion:^(BOOL finished) { + if (finished) + PearlMainQueueAfter( 5, ^{ + [UIView animateWithDuration:0.3f animations:^{ + if (showTips & MPPasswordsBadNameTip) + self.badNameTipContainer.alpha = 0; + }]; + } ); + }]; +} + - (void)fetchedItemsDidUpdate { NSString *query = self.query; diff --git a/MasterPassword/ObjC/iOS/MPPreferencesViewController.m b/MasterPassword/ObjC/iOS/MPPreferencesViewController.m index 78214615..c12f1b7c 100644 --- a/MasterPassword/ObjC/iOS/MPPreferencesViewController.m +++ b/MasterPassword/ObjC/iOS/MPPreferencesViewController.m @@ -31,6 +31,7 @@ inf( @"Preferences will appear" ); [super viewWillAppear:animated]; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.passwordsPreferences"]; MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread]; self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:activeUser.defaultType]; diff --git a/MasterPassword/ObjC/iOS/MPStoreViewController.h b/MasterPassword/ObjC/iOS/MPStoreViewController.h index eb005277..7f53d101 100644 --- a/MasterPassword/ObjC/iOS/MPStoreViewController.h +++ b/MasterPassword/ObjC/iOS/MPStoreViewController.h @@ -17,9 +17,12 @@ @property(weak, nonatomic) IBOutlet MPStoreProductCell *iOSIntegrationCell; @property(weak, nonatomic) IBOutlet MPStoreProductCell *touchIDCell; @property(weak, nonatomic) IBOutlet MPStoreProductCell *fuelCell; +@property(weak, nonatomic) IBOutlet UITableViewCell *loadingCell; @property(weak, nonatomic) IBOutlet NSLayoutConstraint *fuelMeterConstraint; @property(weak, nonatomic) IBOutlet UIButton *fuelSpeedButton; ++ (NSString *)latestStoreFeatures; + @end @interface MPStoreProductCell : UITableViewCell diff --git a/MasterPassword/ObjC/iOS/MPStoreViewController.m b/MasterPassword/ObjC/iOS/MPStoreViewController.m index 21525db6..ea87385c 100644 --- a/MasterPassword/ObjC/iOS/MPStoreViewController.m +++ b/MasterPassword/ObjC/iOS/MPStoreViewController.m @@ -10,6 +10,7 @@ #import "MPiOSAppDelegate.h" #import "UIColor+Expanded.h" #import "MPAppDelegate_InApp.h" +#import "MPPasswordsViewController.h" PearlEnum( MPDevelopmentFuelConsumption, MPDevelopmentFuelConsumptionQuarterly, MPDevelopmentFuelConsumptionMonthly, MPDevelopmentFuelWeekly ); @@ -23,6 +24,22 @@ PearlEnum( MPDevelopmentFuelConsumption, @implementation MPStoreViewController ++ (NSString *)latestStoreFeatures { + + NSMutableString *features = [NSMutableString string]; + NSArray *storeVersions = @[ + @"Generated Usernames\nSecurity Question Answers" + ]; + NSInteger storeVersion = [[NSUserDefaults standardUserDefaults] integerForKey:@"storeVersion"]; + for (; storeVersion < [storeVersions count]; ++storeVersion) + [features appendFormat:@"%@\n", storeVersions[storeVersion]]; + if (![features length]) + return nil; + + [[NSUserDefaults standardUserDefaults] setInteger:storeVersion forKey:@"storeVersion"]; + return features; +} + - (void)viewDidLoad { [super viewDidLoad]; @@ -32,7 +49,6 @@ PearlEnum( MPDevelopmentFuelConsumption, self.tableView.tableHeaderView = [UIView new]; self.tableView.tableFooterView = [UIView new]; - self.tableView.estimatedRowHeight = 400; self.view.backgroundColor = [UIColor clearColor]; } @@ -42,7 +58,7 @@ PearlEnum( MPDevelopmentFuelConsumption, self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 ); - [self reloadCellsHiding:self.allCellsBySection[0] showing:nil]; + [self reloadCellsHiding: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; @@ -80,7 +96,7 @@ PearlEnum( MPDevelopmentFuelConsumption, if (indexPath.section == 0) cell.selectionStyle = [[MPiOSAppDelegate get] isFeatureUnlocked:[self productForCell:cell].productIdentifier]? - UITableViewCellSelectionStyleDefault: UITableViewCellSelectionStyleNone; + UITableViewCellSelectionStyleNone: UITableViewCellSelectionStyleDefault; if (cell.selectionStyle != UITableViewCellSelectionStyleNone) { cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds]; @@ -94,24 +110,19 @@ PearlEnum( MPDevelopmentFuelConsumption, UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath]; [cell layoutIfNeeded]; + [cell layoutIfNeeded]; - return cell.contentView.bounds.size.height; + dbg_return_tr( cell.contentView.bounds.size.height, @ ); } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if (![[MPAppDelegate_Shared get] canMakePayments]) { - [PearlAlert showAlertWithTitle:@"Store Not Set Up" message: - @"Try logging using the App Store or from Settings." - viewStyle:UIAlertViewStyleDefault initAlert:nil - tappedButtonBlock:nil cancelTitle:@"Thanks" otherTitles:nil]; - return; - } - MPStoreProductCell *cell = (MPStoreProductCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath]; - SKProduct *product = [self productForCell:cell]; + if (cell.selectionStyle == UITableViewCellSelectionStyleNone) + return; - if (product) + SKProduct *product = [self productForCell:cell]; + if (product && ![[MPAppDelegate_Shared get] isFeatureUnlocked:product.productIdentifier]) [[MPAppDelegate_Shared get] purchaseProductWithIdentifier:product.productIdentifier quantity:[self quantityForProductIdentifier:product.productIdentifier]]; @@ -140,6 +151,12 @@ PearlEnum( MPDevelopmentFuelConsumption, } cancelTitle:@"Cancel" otherTitles:@"Find Purchases", nil]; } +- (IBAction)sendThanks:(id)sender { + + [[self dismissPopup].navigationController performSegueWithIdentifier:@"web" sender: + [NSURL URLWithString:@"http://thanks.lhunath.com"]]; +} + #pragma mark - MPInAppDelegate - (void)updateWithProducts:(NSArray *)products { @@ -176,6 +193,18 @@ PearlEnum( MPDevelopmentFuelConsumption, #pragma mark - Private +- (MPPasswordsViewController *)dismissPopup { + + for (UIViewController *vc = self; (vc = vc.parentViewController);) + if ([vc isKindOfClass:[MPPasswordsViewController class]]) { + MPPasswordsViewController *passwordsVC = (MPPasswordsViewController *)vc; + [passwordsVC dismissPopdown:self]; + return passwordsVC; + } + + return nil; +} + - (SKProduct *)productForCell:(MPStoreProductCell *)cell { for (SKProduct *product in self.products) @@ -191,6 +220,10 @@ PearlEnum( MPDevelopmentFuelConsumption, return self.generateLoginCell; if ([productIdentifier isEqualToString:MPProductGenerateAnswers]) return self.generateAnswersCell; + if ([productIdentifier isEqualToString:MPProductOSIntegration]) + return self.iOSIntegrationCell; + if ([productIdentifier isEqualToString:MPProductTouchID]) + return self.touchIDCell; if ([productIdentifier isEqualToString:MPProductFuel]) return self.fuelCell; @@ -202,18 +235,18 @@ PearlEnum( MPDevelopmentFuelConsumption, NSMutableArray *showCells = [NSMutableArray array]; NSMutableArray *hideCells = [NSMutableArray array]; [hideCells addObjectsFromArray:self.allCellsBySection[0]]; + [hideCells addObject:self.loadingCell]; for (SKProduct *product in self.products) { [self showCellForProductWithIdentifier:MPProductGenerateLogins ifProduct:product showingCells:showCells]; [self showCellForProductWithIdentifier:MPProductGenerateAnswers ifProduct:product showingCells:showCells]; + [self showCellForProductWithIdentifier:MPProductOSIntegration ifProduct:product showingCells:showCells]; + [self showCellForProductWithIdentifier:MPProductTouchID ifProduct:product showingCells:showCells]; [self showCellForProductWithIdentifier:MPProductFuel ifProduct:product showingCells:showCells]; } [hideCells removeObjectsInArray:showCells]; - if ([self.tableView numberOfRowsInSection:0]) - [self updateCellsHiding:hideCells showing:showCells animation:UITableViewRowAnimationAutomatic]; - else - [self updateCellsHiding:hideCells showing:showCells animation:UITableViewRowAnimationNone]; + [self reloadCellsHiding:hideCells showing:showCells]; } - (void)updateFuel { @@ -221,7 +254,7 @@ PearlEnum( MPDevelopmentFuelConsumption, CGFloat weeklyFuelConsumption = [self weeklyFuelConsumption]; /* consume x fuel / week */ CGFloat fuel = [[MPiOSConfig get].developmentFuel floatValue]; /* x fuel left */ NSDate *now = [NSDate date]; - NSTimeInterval fuelSecondsElapsed = [[MPiOSConfig get].developmentFuelChecked timeIntervalSinceDate:now]; + NSTimeInterval fuelSecondsElapsed = -[[MPiOSConfig get].developmentFuelChecked timeIntervalSinceDate:now]; if (fuelSecondsElapsed > 3600 || ![MPiOSConfig get].developmentFuelChecked) { NSTimeInterval weeksElapsed = fuelSecondsElapsed / (3600 * 24 * 7 /* 1 week */); /* x weeks elapsed */ fuel -= weeklyFuelConsumption * weeksElapsed; diff --git a/MasterPassword/ObjC/iOS/MPUsersViewController.h b/MasterPassword/ObjC/iOS/MPUsersViewController.h index dac5b01a..1c3de39e 100644 --- a/MasterPassword/ObjC/iOS/MPUsersViewController.h +++ b/MasterPassword/ObjC/iOS/MPUsersViewController.h @@ -16,25 +16,26 @@ // Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. // - @interface MPUsersViewController : UIViewController @property(weak, nonatomic) IBOutlet UIView *userSelectionContainer; @property(weak, nonatomic) IBOutlet UIButton *marqueeButton; -@property(weak, nonatomic) IBOutlet UIView *gitTipTip; @property(weak, nonatomic) IBOutlet UITextField *entryField; @property(weak, nonatomic) IBOutlet UILabel *entryLabel; @property(weak, nonatomic) IBOutlet UILabel *entryTipTitleLabel; @property(weak, nonatomic) IBOutlet UILabel *entryTipSubtitleLabel; -@property(weak, nonatomic) IBOutlet UIView *entryTipContainer; @property(weak, nonatomic) IBOutlet UIView *entryContainer; @property(weak, nonatomic) IBOutlet UIView *footerContainer; @property(weak, nonatomic) IBOutlet UIActivityIndicatorView *storeLoadingActivity; @property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView; -@property (strong, nonatomic) IBOutlet UIButton *nextAvatarButton; -@property (strong, nonatomic) IBOutlet UIButton *previousAvatarButton; +@property(weak, nonatomic) IBOutlet UIView *avatarTipContainer; +@property(weak, nonatomic) IBOutlet UIView *entryTipContainer; +@property(weak, nonatomic) IBOutlet UIView *preferencesTipContainer; +@property(weak, nonatomic) IBOutlet UIView *thanksTipContainer; +@property(weak, nonatomic) IBOutlet UIButton *nextAvatarButton; +@property(weak, nonatomic) IBOutlet UIButton *previousAvatarButton; -@property(assign, nonatomic) BOOL active; +@property(assign, nonatomic, readonly) BOOL active; - (void)setActive:(BOOL)active animated:(BOOL)animated; - (IBAction)changeAvatar:(UIButton *)sender; diff --git a/MasterPassword/ObjC/iOS/MPUsersViewController.m b/MasterPassword/ObjC/iOS/MPUsersViewController.m index 18e645ea..de63dd6a 100644 --- a/MasterPassword/ObjC/iOS/MPUsersViewController.m +++ b/MasterPassword/ObjC/iOS/MPUsersViewController.m @@ -24,6 +24,13 @@ #import "MPAppDelegate_Key.h" #import "MPWebViewController.h" +typedef NS_OPTIONS( NSUInteger, MPUsersTips ) { + MPUsersThanksTip = 1 << 0, + MPUsersAvatarTip = 1 << 1, + MPUsersMasterPasswordTip = 1 << 2, + MPUsersPreferencesTip = 1 << 3, +}; + typedef NS_ENUM( NSUInteger, MPActiveUserState ) { /** The users are all inactive */ MPActiveUserStateNone, @@ -69,7 +76,10 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) { self.avatarCollectionView.allowsMultipleSelection = YES; [self.entryField addTarget:self action:@selector( textFieldEditingChanged: ) forControlEvents:UIControlEventEditingChanged]; + self.preferencesTipContainer.alpha = 0; + [self setActive:YES animated:NO]; + [self showTips:MPUsersThanksTip]; } - (void)viewWillAppear:(BOOL)animated { @@ -391,34 +401,23 @@ referenceSizeForFooterInSection:(NSInteger)section { if (buttonIndex == [sheet destructiveButtonIndex]) { // Delete User - [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context]; - if (!user_) - return; - - [context deleteObject:user_]; - [context saveToStore]; - [self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore. - }]; + [PearlAlert showParentalGateWithTitle:@"Deleting User" message: + @"The user and its sites will be deleted.\nPlease confirm by solving:" + completion:^(BOOL continuing) { + if (continuing) + [self deleteUser:userID]; + }]; return; } if (buttonIndex == [sheet firstOtherButtonIndex]) // Reset Password - [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context]; - if (!user_) - return; - - [[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{ - PearlMainQueue( ^{ - NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell]; - [self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO - scrollPosition:UICollectionViewScrollPositionNone]; - [self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath]; - } ); - }]; - }]; + [PearlAlert showParentalGateWithTitle:@"Resetting User" message: + @"The user's master password will be reset.\nPlease confirm by solving:" + completion:^(BOOL continuing) { + if (continuing) + [self resetUser:userID avatar:avatarCell]; + }]; } cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil]; } @@ -441,6 +440,66 @@ referenceSizeForFooterInSection:(NSInteger)section { #pragma mark - Private +- (void)deleteUser:(NSManagedObjectID *)userID { + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPUserEntity + *user_ = [MPUserEntity existingObjectWithID:userID inContext:context]; + if (!user_) + return; + + [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_) + return; + + [[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{ + PearlMainQueue( ^{ + NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell]; + [self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO + scrollPosition:UICollectionViewScrollPositionNone]; + [self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath]; + } ); + }]; + }]; +} + +- (void)showTips:(MPUsersTips)showTips { + + [UIView animateWithDuration:0.3f animations:^{ + if (showTips & MPUsersThanksTip) + self.thanksTipContainer.alpha = 1; + if (showTips & MPUsersAvatarTip) + self.avatarTipContainer.alpha = 1; + if (showTips & MPUsersMasterPasswordTip) + self.entryTipContainer.alpha = 1; + if (showTips & MPUsersPreferencesTip) + self.preferencesTipContainer.alpha = 1; + } completion:^(BOOL finished) { + if (finished) + PearlMainQueueAfter( 5, ^{ + [UIView animateWithDuration:0.3f animations:^{ + if (showTips & MPUsersThanksTip) + self.thanksTipContainer.alpha = 0; + if (showTips & MPUsersAvatarTip) + self.avatarTipContainer.alpha = 0; + if (showTips & MPUsersMasterPasswordTip) + self.entryTipContainer.alpha = 0; + if (showTips & MPUsersPreferencesTip) + self.preferencesTipContainer.alpha = 0; + }]; + } ); + }]; +} + - (void)showEntryTip:(NSString *)message { NSUInteger newlineIndex = [message rangeOfString:@"\n"].location; @@ -448,17 +507,7 @@ referenceSizeForFooterInSection:(NSInteger)section { NSString *messageSubtitle = newlineIndex == NSNotFound? nil: [message substringFromIndex:newlineIndex]; self.entryTipTitleLabel.text = messageTitle; self.entryTipSubtitleLabel.text = messageSubtitle; - - [UIView animateWithDuration:0.3f animations:^{ - self.entryTipContainer.alpha = 1; - } completion:^(BOOL finished) { - if (finished) - PearlMainQueueAfter( 4, ^{ - [UIView animateWithDuration:0.3f animations:^{ - self.entryTipContainer.alpha = 0; - }]; - } ); - }]; + [self showTips:MPUsersMasterPasswordTip]; } - (void)firedMarqueeTimer:(NSTimer *)timer { @@ -610,16 +659,16 @@ referenceSizeForFooterInSection:(NSInteger)section { [self.storeLoadingActivity startAnimating]; if (mainContext) - PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, mainContext, [NSOperationQueue mainQueue], - ^(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( NSManagedObjectContextObjectsDidChangeNotification, mainContext, [NSOperationQueue mainQueue], + ^(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, nil, [NSOperationQueue mainQueue], ^(MPUsersViewController *self, NSNotification *note) { self.userIDs = nil; @@ -657,11 +706,6 @@ referenceSizeForFooterInSection:(NSInteger)section { #pragma mark - Properties -- (void)setActive:(BOOL)active { - - [self setActive:active animated:NO]; -} - - (void)setActive:(BOOL)active animated:(BOOL)animated { _active = active; @@ -777,6 +821,29 @@ referenceSizeForFooterInSection:(NSInteger)section { } } + // Manage tip visibility. + switch (activeUserState) { + case MPActiveUserStateNone: + case MPActiveUserStateMasterPasswordConfirmation: + case MPActiveUserStateLogin: { + break; + } + case MPActiveUserStateUserName: { + [self showTips:MPUsersAvatarTip]; + break; + } + case MPActiveUserStateMasterPasswordChoice: { + [self showEntryTip:strl( @"A short phrase makes a strong, memorable password." )]; + break; + } + case MPActiveUserStateMinimized: { + if (YES || ![[NSUserDefaults standardUserDefaults] boolForKey:@"tipped.passwordsPreferences"]) + [self showTips:MPUsersPreferencesTip]; + + break; + } + } + [self.view layoutIfNeeded]; } completion:^(BOOL finished) { [_afterUpdates setSuspended:NO]; diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index b918993e..473e615b 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -10,6 +10,7 @@ #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" #import "IASKSettingsReader.h" +#import "MPStoreViewController.h" @interface MPiOSAppDelegate() @@ -130,6 +131,14 @@ [self.navigationController performSegueWithIdentifier:@"setup" sender:self]; } ); + NSString *latestFeatures = [MPStoreViewController latestStoreFeatures]; + if (latestFeatures) + [PearlAlert showAlertWithTitle:@"New Features" message: + strf( @"The following features are now available in the store:\n\n%@•••\n\n" + @"Find the store from the user pull‑down after logging in.", latestFeatures ) + viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil + cancelTitle:@"Thanks" otherTitles:nil]; + MPCheckpoint( MPCheckpointStarted, @{ @"simulator" : PearlStringB( [PearlDeviceUtils isSimulator] ), @"encrypted" : PearlStringB( [PearlDeviceUtils isAppEncrypted] ), diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index 4349ff57..196fa088 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 93D39262A8A97DB748213309 /* PearlEMail.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393BB973253D4BAAC84AA /* PearlEMail.m */; }; 93D392A8777DC30C11361647 /* UITextView+PearlAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */; }; 93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; }; + 93D392FD5E2052F7D7DB3774 /* NSString+MPMarkDown.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */; }; 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 */; }; @@ -57,28 +58,6 @@ DA071BF3190187FE00179766 /* empty@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA071BF1190187FE00179766 /* empty@2x.png */; }; DA071BF4190187FE00179766 /* empty.png in Resources */ = {isa = PBXBuildFile; fileRef = DA071BF2190187FE00179766 /* empty.png */; }; DA095E75172F4CD8001C948B /* MPLogsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */; }; - DA2509FD1956484D00AC23F1 /* image-10@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509E31956484D00AC23F1 /* image-10@2x.png */; }; - DA2509FE1956484D00AC23F1 /* image-10.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509E41956484D00AC23F1 /* image-10.png */; }; - DA2509FF1956484D00AC23F1 /* image-9@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509E51956484D00AC23F1 /* image-9@2x.png */; }; - DA250A001956484D00AC23F1 /* image-9.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509E61956484D00AC23F1 /* image-9.png */; }; - DA250A011956484D00AC23F1 /* image-8@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509E71956484D00AC23F1 /* image-8@2x.png */; }; - DA250A021956484D00AC23F1 /* image-8.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509E81956484D00AC23F1 /* image-8.png */; }; - DA250A031956484D00AC23F1 /* image-7@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509E91956484D00AC23F1 /* image-7@2x.png */; }; - DA250A041956484D00AC23F1 /* image-7.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509EA1956484D00AC23F1 /* image-7.png */; }; - DA250A051956484D00AC23F1 /* image-6@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509EB1956484D00AC23F1 /* image-6@2x.png */; }; - DA250A061956484D00AC23F1 /* image-6.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509EC1956484D00AC23F1 /* image-6.png */; }; - DA250A071956484D00AC23F1 /* image-5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509ED1956484D00AC23F1 /* image-5@2x.png */; }; - DA250A081956484D00AC23F1 /* image-5.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509EE1956484D00AC23F1 /* image-5.png */; }; - DA250A091956484D00AC23F1 /* image-4@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509EF1956484D00AC23F1 /* image-4@2x.png */; }; - DA250A0A1956484D00AC23F1 /* image-4.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F01956484D00AC23F1 /* image-4.png */; }; - DA250A0B1956484D00AC23F1 /* image-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F11956484D00AC23F1 /* image-3@2x.png */; }; - DA250A0C1956484D00AC23F1 /* image-3.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F21956484D00AC23F1 /* image-3.png */; }; - DA250A0D1956484D00AC23F1 /* image-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F31956484D00AC23F1 /* image-2@2x.png */; }; - DA250A0E1956484D00AC23F1 /* image-2.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F41956484D00AC23F1 /* image-2.png */; }; - DA250A0F1956484D00AC23F1 /* image-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F51956484D00AC23F1 /* image-1@2x.png */; }; - DA250A101956484D00AC23F1 /* image-1.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F61956484D00AC23F1 /* image-1.png */; }; - DA250A111956484D00AC23F1 /* image-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F71956484D00AC23F1 /* image-0@2x.png */; }; - DA250A121956484D00AC23F1 /* image-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DA2509F81956484D00AC23F1 /* image-0.png */; }; DA250A17195665A100AC23F1 /* UITableView+PearlReloadFromArray.m in Sources */ = {isa = PBXBuildFile; fileRef = DA250A13195665A100AC23F1 /* UITableView+PearlReloadFromArray.m */; }; DA250A18195665A100AC23F1 /* UITableView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = DA250A14195665A100AC23F1 /* UITableView+PearlReloadFromArray.h */; }; DA250A19195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA250A15195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m */; }; @@ -190,6 +169,40 @@ DAA141201922FF020032B392 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA1411C1922FF020032B392 /* PearlTween.m */; }; DAA141211922FF020032B392 /* PearlTween.h in Headers */ = {isa = PBXBuildFile; fileRef = DAA1411D1922FF020032B392 /* PearlTween.h */; }; DAA141221922FF020032B392 /* map-macro.h in Headers */ = {isa = PBXBuildFile; fileRef = DAA1411F1922FF020032B392 /* map-macro.h */; }; + DAA175F519D86C620044227B /* markdown_lib.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA175B719D86C620044227B /* markdown_lib.m */; }; + DAA175F619D86C620044227B /* markdown_output.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA175B819D86C620044227B /* markdown_output.m */; }; + DAA175F719D86C620044227B /* markdown_parser.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA175BA19D86C620044227B /* markdown_parser.m */; }; + DAA1760F19D86C620044227B /* README.markdown in Sources */ = {isa = PBXBuildFile; fileRef = DAA175ED19D86C620044227B /* README.markdown */; }; + DAA1761019D86C620044227B /* README_PEG-MARKDWON.markdown in Sources */ = {isa = PBXBuildFile; fileRef = DAA175EE19D86C620044227B /* README_PEG-MARKDWON.markdown */; }; + DAA1761B19D86D0D0044227B /* libAttributedMarkdown.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAA1757D19D86BE70044227B /* libAttributedMarkdown.a */; }; + DAA1762319D89B600044227B /* thumb_touch_id@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1761D19D89B600044227B /* thumb_touch_id@3x.png */; }; + DAA1762419D89B610044227B /* thumb_touch_id@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1761E19D89B600044227B /* thumb_touch_id@2x.png */; }; + DAA1762519D89B610044227B /* thumb_touch_id.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1761F19D89B600044227B /* thumb_touch_id.png */; }; + DAA1762619D89B610044227B /* thumb_ios_integration@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762019D89B600044227B /* thumb_ios_integration@3x.png */; }; + DAA1762719D89B610044227B /* thumb_ios_integration@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762119D89B600044227B /* thumb_ios_integration@2x.png */; }; + DAA1762819D89B610044227B /* thumb_ios_integration.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762219D89B600044227B /* thumb_ios_integration.png */; }; + DAA1763F19D8B82B0044227B /* site_new@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762919D8B82B0044227B /* site_new@2x.png */; }; + DAA1764019D8B82B0044227B /* site_new.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762A19D8B82B0044227B /* site_new.png */; }; + DAA1764119D8B82B0044227B /* settings@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762B19D8B82B0044227B /* settings@2x.png */; }; + DAA1764219D8B82B0044227B /* settings.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762C19D8B82B0044227B /* settings.png */; }; + DAA1764319D8B82B0044227B /* personal_pw@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762D19D8B82B0044227B /* personal_pw@2x.png */; }; + DAA1764419D8B82B0044227B /* personal_pw.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762E19D8B82B0044227B /* personal_pw.png */; }; + DAA1764519D8B82B0044227B /* name_new@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1762F19D8B82B0044227B /* name_new@2x.png */; }; + DAA1764619D8B82B0044227B /* name_new.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763019D8B82B0044227B /* name_new.png */; }; + DAA1764719D8B82B0044227B /* mpw_new@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763119D8B82B0044227B /* mpw_new@2x.png */; }; + DAA1764819D8B82B0044227B /* mpw_new.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763219D8B82B0044227B /* mpw_new.png */; }; + DAA1764919D8B82B0044227B /* login_new@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763319D8B82B0044227B /* login_new@2x.png */; }; + DAA1764A19D8B82B0044227B /* login_new.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763419D8B82B0044227B /* login_new.png */; }; + DAA1764B19D8B82B0044227B /* login_name@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763519D8B82B0044227B /* login_name@2x.png */; }; + DAA1764C19D8B82B0044227B /* login_name.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763619D8B82B0044227B /* login_name.png */; }; + DAA1764D19D8B82B0044227B /* initial@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763719D8B82B0044227B /* initial@2x.png */; }; + DAA1764E19D8B82B0044227B /* initial.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763819D8B82B0044227B /* initial.png */; }; + DAA1764F19D8B82B0044227B /* counter@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763919D8B82B0044227B /* counter@2x.png */; }; + DAA1765019D8B82B0044227B /* counter.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763A19D8B82B0044227B /* counter.png */; }; + DAA1765119D8B82B0044227B /* copy_pw@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763B19D8B82B0044227B /* copy_pw@2x.png */; }; + DAA1765219D8B82B0044227B /* copy_pw.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763C19D8B82B0044227B /* copy_pw.png */; }; + DAA1765319D8B82B0044227B /* choose_type@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763D19D8B82B0044227B /* choose_type@2x.png */; }; + DAA1765419D8B82B0044227B /* choose_type.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763E19D8B82B0044227B /* choose_type.png */; }; DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; }; DABD39371711E29700CF925C /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366C1711E29400CF925C /* avatar-0.png */; }; DABD39381711E29700CF925C /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366D1711E29400CF925C /* avatar-0@2x.png */; }; @@ -421,6 +434,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DAA1757B19D86BE70044227B /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -442,6 +464,7 @@ 93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.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 = ""; }; + 93D393CB0B1F4EC8C17CFE43 /* NSString+MPMarkDown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MPMarkDown.h"; sourceTree = ""; }; 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 = ""; }; 93D394482BB07F90E8FD1314 /* UIResponder+PearlFirstResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIResponder+PearlFirstResponder.h"; sourceTree = ""; }; @@ -478,6 +501,7 @@ 93D39B381350802A194BF332 /* MPAvatarCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAvatarCell.m; sourceTree = ""; }; 93D39B455A71EC98C749E623 /* MPOverlayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOverlayViewController.h; sourceTree = ""; }; 93D39BAA71DE51B4D8A1286C /* MPCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCell.m; sourceTree = ""; }; + 93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MPMarkDown.m"; sourceTree = ""; }; 93D39C426E03358384018E85 /* MPAnswersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnswersViewController.m; sourceTree = ""; }; 93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsSegue.h; sourceTree = ""; }; 93D39C86E984EC65DA5ACB1D /* MPAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppSettingsViewController.h; sourceTree = ""; }; @@ -497,28 +521,6 @@ DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; DA071BF1190187FE00179766 /* empty@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "empty@2x.png"; sourceTree = ""; }; DA071BF2190187FE00179766 /* empty.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = empty.png; sourceTree = ""; }; - DA2509E31956484D00AC23F1 /* image-10@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-10@2x.png"; sourceTree = ""; }; - DA2509E41956484D00AC23F1 /* image-10.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-10.png"; sourceTree = ""; }; - DA2509E51956484D00AC23F1 /* image-9@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-9@2x.png"; sourceTree = ""; }; - DA2509E61956484D00AC23F1 /* image-9.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-9.png"; sourceTree = ""; }; - DA2509E71956484D00AC23F1 /* image-8@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-8@2x.png"; sourceTree = ""; }; - DA2509E81956484D00AC23F1 /* image-8.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-8.png"; sourceTree = ""; }; - DA2509E91956484D00AC23F1 /* image-7@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-7@2x.png"; sourceTree = ""; }; - DA2509EA1956484D00AC23F1 /* image-7.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-7.png"; sourceTree = ""; }; - DA2509EB1956484D00AC23F1 /* image-6@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-6@2x.png"; sourceTree = ""; }; - DA2509EC1956484D00AC23F1 /* image-6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-6.png"; sourceTree = ""; }; - DA2509ED1956484D00AC23F1 /* image-5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-5@2x.png"; sourceTree = ""; }; - DA2509EE1956484D00AC23F1 /* image-5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-5.png"; sourceTree = ""; }; - DA2509EF1956484D00AC23F1 /* image-4@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-4@2x.png"; sourceTree = ""; }; - DA2509F01956484D00AC23F1 /* image-4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-4.png"; sourceTree = ""; }; - DA2509F11956484D00AC23F1 /* image-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-3@2x.png"; sourceTree = ""; }; - DA2509F21956484D00AC23F1 /* image-3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-3.png"; sourceTree = ""; }; - DA2509F31956484D00AC23F1 /* image-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-2@2x.png"; sourceTree = ""; }; - DA2509F41956484D00AC23F1 /* image-2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-2.png"; sourceTree = ""; }; - DA2509F51956484D00AC23F1 /* image-1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-1@2x.png"; sourceTree = ""; }; - DA2509F61956484D00AC23F1 /* image-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-1.png"; sourceTree = ""; }; - DA2509F71956484D00AC23F1 /* image-0@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-0@2x.png"; sourceTree = ""; }; - DA2509F81956484D00AC23F1 /* image-0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-0.png"; sourceTree = ""; }; DA250A13195665A100AC23F1 /* UITableView+PearlReloadFromArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITableView+PearlReloadFromArray.m"; sourceTree = ""; }; DA250A14195665A100AC23F1 /* UITableView+PearlReloadFromArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITableView+PearlReloadFromArray.h"; sourceTree = ""; }; DA250A15195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionReusableView+PearlDequeue.m"; sourceTree = ""; }; @@ -608,6 +610,48 @@ DAA1411C1922FF020032B392 /* PearlTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlTween.m; sourceTree = ""; }; DAA1411D1922FF020032B392 /* PearlTween.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlTween.h; sourceTree = ""; }; DAA1411F1922FF020032B392 /* map-macro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "map-macro.h"; sourceTree = ""; }; + DAA1757D19D86BE70044227B /* libAttributedMarkdown.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAttributedMarkdown.a; sourceTree = BUILT_PRODUCTS_DIR; }; + DAA175B119D86C620044227B /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + DAA175B619D86C620044227B /* markdown_lib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = markdown_lib.h; sourceTree = ""; }; + DAA175B719D86C620044227B /* markdown_lib.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = markdown_lib.m; sourceTree = ""; }; + DAA175B819D86C620044227B /* markdown_output.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = markdown_output.m; sourceTree = ""; }; + DAA175B919D86C620044227B /* markdown_parser.leg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = markdown_parser.leg; sourceTree = ""; }; + DAA175BA19D86C620044227B /* markdown_parser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = markdown_parser.m; sourceTree = ""; }; + DAA175BB19D86C620044227B /* markdown_peg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = markdown_peg.h; sourceTree = ""; }; + DAA175EB19D86C620044227B /* parsing_functions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = parsing_functions.m; sourceTree = ""; }; + DAA175EC19D86C620044227B /* platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform.h; sourceTree = ""; }; + DAA175ED19D86C620044227B /* README.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.markdown; sourceTree = ""; }; + DAA175EE19D86C620044227B /* README_PEG-MARKDWON.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "README_PEG-MARKDWON.markdown"; sourceTree = ""; }; + DAA175EF19D86C620044227B /* utility_functions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = utility_functions.m; sourceTree = ""; }; + DAA1761C19D86E800044227B /* attributed-markdown.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "attributed-markdown.pch"; sourceTree = ""; }; + DAA1761D19D89B600044227B /* thumb_touch_id@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_touch_id@3x.png"; sourceTree = ""; }; + DAA1761E19D89B600044227B /* thumb_touch_id@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_touch_id@2x.png"; sourceTree = ""; }; + DAA1761F19D89B600044227B /* thumb_touch_id.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumb_touch_id.png; sourceTree = ""; }; + DAA1762019D89B600044227B /* thumb_ios_integration@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_ios_integration@3x.png"; sourceTree = ""; }; + DAA1762119D89B600044227B /* thumb_ios_integration@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_ios_integration@2x.png"; sourceTree = ""; }; + DAA1762219D89B600044227B /* thumb_ios_integration.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumb_ios_integration.png; sourceTree = ""; }; + DAA1762919D8B82B0044227B /* site_new@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "site_new@2x.png"; sourceTree = ""; }; + DAA1762A19D8B82B0044227B /* site_new.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = site_new.png; sourceTree = ""; }; + DAA1762B19D8B82B0044227B /* settings@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "settings@2x.png"; sourceTree = ""; }; + DAA1762C19D8B82B0044227B /* settings.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = settings.png; sourceTree = ""; }; + DAA1762D19D8B82B0044227B /* personal_pw@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "personal_pw@2x.png"; sourceTree = ""; }; + DAA1762E19D8B82B0044227B /* personal_pw.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = personal_pw.png; sourceTree = ""; }; + DAA1762F19D8B82B0044227B /* name_new@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "name_new@2x.png"; sourceTree = ""; }; + DAA1763019D8B82B0044227B /* name_new.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = name_new.png; sourceTree = ""; }; + DAA1763119D8B82B0044227B /* mpw_new@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "mpw_new@2x.png"; sourceTree = ""; }; + DAA1763219D8B82B0044227B /* mpw_new.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mpw_new.png; sourceTree = ""; }; + DAA1763319D8B82B0044227B /* login_new@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "login_new@2x.png"; sourceTree = ""; }; + DAA1763419D8B82B0044227B /* login_new.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = login_new.png; sourceTree = ""; }; + DAA1763519D8B82B0044227B /* login_name@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "login_name@2x.png"; sourceTree = ""; }; + DAA1763619D8B82B0044227B /* login_name.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = login_name.png; sourceTree = ""; }; + DAA1763719D8B82B0044227B /* initial@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "initial@2x.png"; sourceTree = ""; }; + DAA1763819D8B82B0044227B /* initial.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = initial.png; sourceTree = ""; }; + DAA1763919D8B82B0044227B /* counter@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "counter@2x.png"; sourceTree = ""; }; + DAA1763A19D8B82B0044227B /* counter.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = counter.png; sourceTree = ""; }; + DAA1763B19D8B82B0044227B /* copy_pw@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "copy_pw@2x.png"; sourceTree = ""; }; + DAA1763C19D8B82B0044227B /* copy_pw.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = copy_pw.png; sourceTree = ""; }; + DAA1763D19D8B82B0044227B /* choose_type@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "choose_type@2x.png"; sourceTree = ""; }; + DAA1763E19D8B82B0044227B /* choose_type.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = choose_type.png; sourceTree = ""; }; DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; DABD360F1711E29400CF925C /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = ""; }; @@ -1433,6 +1477,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DAA1761B19D86D0D0044227B /* libAttributedMarkdown.a in Frameworks */, DA32D03E19D11293004F3F0E /* libKCOrderedAccessorFix.a in Frameworks */, DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */, DAE2725A19C93B8E007C5262 /* StoreKit.framework in Frameworks */, @@ -1457,6 +1502,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DAA1757A19D86BE70044227B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DAC6325A1486805C0075AEA5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1498,28 +1550,28 @@ DA2509B619563E1E00AC23F1 /* Guide */ = { isa = PBXGroup; children = ( - DA2509E31956484D00AC23F1 /* image-10@2x.png */, - DA2509E41956484D00AC23F1 /* image-10.png */, - DA2509E51956484D00AC23F1 /* image-9@2x.png */, - DA2509E61956484D00AC23F1 /* image-9.png */, - DA2509E71956484D00AC23F1 /* image-8@2x.png */, - DA2509E81956484D00AC23F1 /* image-8.png */, - DA2509E91956484D00AC23F1 /* image-7@2x.png */, - DA2509EA1956484D00AC23F1 /* image-7.png */, - DA2509EB1956484D00AC23F1 /* image-6@2x.png */, - DA2509EC1956484D00AC23F1 /* image-6.png */, - DA2509ED1956484D00AC23F1 /* image-5@2x.png */, - DA2509EE1956484D00AC23F1 /* image-5.png */, - DA2509EF1956484D00AC23F1 /* image-4@2x.png */, - DA2509F01956484D00AC23F1 /* image-4.png */, - DA2509F11956484D00AC23F1 /* image-3@2x.png */, - DA2509F21956484D00AC23F1 /* image-3.png */, - DA2509F31956484D00AC23F1 /* image-2@2x.png */, - DA2509F41956484D00AC23F1 /* image-2.png */, - DA2509F51956484D00AC23F1 /* image-1@2x.png */, - DA2509F61956484D00AC23F1 /* image-1.png */, - DA2509F71956484D00AC23F1 /* image-0@2x.png */, - DA2509F81956484D00AC23F1 /* image-0.png */, + DAA1762919D8B82B0044227B /* site_new@2x.png */, + DAA1762A19D8B82B0044227B /* site_new.png */, + DAA1762B19D8B82B0044227B /* settings@2x.png */, + DAA1762C19D8B82B0044227B /* settings.png */, + DAA1762D19D8B82B0044227B /* personal_pw@2x.png */, + DAA1762E19D8B82B0044227B /* personal_pw.png */, + DAA1762F19D8B82B0044227B /* name_new@2x.png */, + DAA1763019D8B82B0044227B /* name_new.png */, + DAA1763119D8B82B0044227B /* mpw_new@2x.png */, + DAA1763219D8B82B0044227B /* mpw_new.png */, + DAA1763319D8B82B0044227B /* login_new@2x.png */, + DAA1763419D8B82B0044227B /* login_new.png */, + DAA1763519D8B82B0044227B /* login_name@2x.png */, + DAA1763619D8B82B0044227B /* login_name.png */, + DAA1763719D8B82B0044227B /* initial@2x.png */, + DAA1763819D8B82B0044227B /* initial.png */, + DAA1763919D8B82B0044227B /* counter@2x.png */, + DAA1763A19D8B82B0044227B /* counter.png */, + DAA1763B19D8B82B0044227B /* copy_pw@2x.png */, + DAA1763C19D8B82B0044227B /* copy_pw.png */, + DAA1763D19D8B82B0044227B /* choose_type@2x.png */, + DAA1763E19D8B82B0044227B /* choose_type.png */, ); path = Guide; sourceTree = ""; @@ -1547,6 +1599,8 @@ 93D39D6604447D7708039155 /* MPAnswersViewController.h */, 93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */, 93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */, + 93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */, + 93D393CB0B1F4EC8C17CFE43 /* NSString+MPMarkDown.h */, ); sourceTree = ""; }; @@ -1559,6 +1613,7 @@ DAC6326C148680650075AEA5 /* libjrswizzle.a */, DAFC5655172C573B00CB5CC5 /* libInAppSettingsKit.a */, DA32D02019D111C6004F3F0E /* libKCOrderedAccessorFix.a */, + DAA1757D19D86BE70044227B /* libAttributedMarkdown.a */, ); name = Products; sourceTree = ""; @@ -1625,9 +1680,35 @@ path = include; sourceTree = ""; }; + DAA1759319D86C610044227B /* AttributedMarkdown */ = { + isa = PBXGroup; + children = ( + DAA1761C19D86E800044227B /* attributed-markdown.pch */, + DAA175B119D86C620044227B /* LICENSE */, + DAA175B619D86C620044227B /* markdown_lib.h */, + DAA175B719D86C620044227B /* markdown_lib.m */, + DAA175B819D86C620044227B /* markdown_output.m */, + DAA175B919D86C620044227B /* markdown_parser.leg */, + DAA175BA19D86C620044227B /* markdown_parser.m */, + DAA175BB19D86C620044227B /* markdown_peg.h */, + DAA175EB19D86C620044227B /* parsing_functions.m */, + DAA175EC19D86C620044227B /* platform.h */, + DAA175EE19D86C620044227B /* README_PEG-MARKDWON.markdown */, + DAA175ED19D86C620044227B /* README.markdown */, + DAA175EF19D86C620044227B /* utility_functions.m */, + ); + path = AttributedMarkdown; + sourceTree = ""; + }; DABD360D1711E29400CF925C /* Media */ = { isa = PBXGroup; children = ( + DAA1761D19D89B600044227B /* thumb_touch_id@3x.png */, + DAA1761E19D89B600044227B /* thumb_touch_id@2x.png */, + DAA1761F19D89B600044227B /* thumb_touch_id.png */, + DAA1762019D89B600044227B /* thumb_ios_integration@3x.png */, + DAA1762119D89B600044227B /* thumb_ios_integration@2x.png */, + DAA1762219D89B600044227B /* thumb_ios_integration.png */, DA32D07719D7D784004F3F0E /* background@3x.png */, DA32D07819D7D784004F3F0E /* background@2x.png */, DA32D07919D7D784004F3F0E /* background.png */, @@ -2446,6 +2527,7 @@ DACA22121705DDC5002C6C22 /* External */ = { isa = PBXGroup; children = ( + DAA1759319D86C610044227B /* AttributedMarkdown */, DA32D03719D111EB004F3F0E /* KCOrderedAccessorFix */, DAA141181922FED80032B392 /* iOS */, DAFC5662172C57EC00CB5CC5 /* InAppSettingsKit */, @@ -2894,6 +2976,23 @@ productReference = DA5BFA44147E415C00F98B1E /* MasterPassword.app */; productType = "com.apple.product-type.application"; }; + DAA1757C19D86BE70044227B /* AttributedMarkdown */ = { + isa = PBXNativeTarget; + buildConfigurationList = DAA1758B19D86BE80044227B /* Build configuration list for PBXNativeTarget "AttributedMarkdown" */; + buildPhases = ( + DAA1757919D86BE70044227B /* Sources */, + DAA1757A19D86BE70044227B /* Frameworks */, + DAA1757B19D86BE70044227B /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AttributedMarkdown; + productName = AttributedMarkdown; + productReference = DAA1757D19D86BE70044227B /* libAttributedMarkdown.a */; + productType = "com.apple.product-type.library.static"; + }; DAC6325C1486805C0075AEA5 /* uicolor-utilities */ = { isa = PBXNativeTarget; buildConfigurationList = DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */; @@ -2991,6 +3090,9 @@ }; }; }; + DAA1757C19D86BE70044227B = { + CreatedOnToolsVersion = 6.0; + }; }; }; buildConfigurationList = DA5BFA3E147E415C00F98B1E /* Build configuration list for PBXProject "MasterPassword-iOS" */; @@ -3096,6 +3198,7 @@ DAC6326B148680650075AEA5 /* jrswizzle */, DAFC5654172C573B00CB5CC5 /* InAppSettingsKit */, DA32D01F19D111C6004F3F0E /* KCOrderedAccessorFix */, + DAA1757C19D86BE70044227B /* AttributedMarkdown */, ); }; /* End PBXProject section */ @@ -3109,7 +3212,6 @@ DACA296F1705DF81002C6C22 /* Crashlytics.plist in Resources */, DACA29731705E1A8002C6C22 /* ciphers.plist in Resources */, DA32D04F19D2F59B004F3F0E /* meter_fuel@2x.png in Resources */, - DA250A0F1956484D00AC23F1 /* image-1@2x.png in Resources */, DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */, DA45224C190628B2008F650A /* icon_gear@2x.png in Resources */, DA854C8318D4CFBF00106317 /* avatar-add@2x.png in Resources */, @@ -3122,14 +3224,13 @@ DA32D04919D2F417004F3F0E /* thumb_fuel@2x.png in Resources */, DABD39371711E29700CF925C /* avatar-0.png in Resources */, DABD39381711E29700CF925C /* avatar-0@2x.png in Resources */, - DA250A041956484D00AC23F1 /* image-7.png in Resources */, + DAA1764819D8B82B0044227B /* mpw_new.png in Resources */, DA25C5FC197CCAF70046CDCF /* icon_list-names.png in Resources */, DABD39391711E29700CF925C /* avatar-1.png in Resources */, DA7304E5194E025900E72520 /* tip_basic_black.png in Resources */, DABD393A1711E29700CF925C /* avatar-10.png in Resources */, DABD393B1711E29700CF925C /* avatar-10@2x.png in Resources */, DABD393C1711E29700CF925C /* avatar-11.png in Resources */, - DA250A0A1956484D00AC23F1 /* image-4.png in Resources */, DABD393D1711E29700CF925C /* avatar-11@2x.png in Resources */, DA73049D194E022700E72520 /* ui_spinner.png in Resources */, DABD393E1711E29700CF925C /* avatar-12.png in Resources */, @@ -3140,67 +3241,74 @@ DABD39411711E29700CF925C /* avatar-13@2x.png in Resources */, DABD39421711E29700CF925C /* avatar-14.png in Resources */, DABD39431711E29700CF925C /* avatar-14@2x.png in Resources */, + DAA1764419D8B82B0044227B /* personal_pw.png in Resources */, DABD39441711E29700CF925C /* avatar-15.png in Resources */, DABD39451711E29700CF925C /* avatar-15@2x.png in Resources */, + DAA1762419D89B610044227B /* thumb_touch_id@2x.png in Resources */, DABD39461711E29700CF925C /* avatar-16.png in Resources */, DABD39471711E29700CF925C /* avatar-16@2x.png in Resources */, DA7304E7194E027C00E72520 /* Square-bottom.png in Resources */, - DA250A091956484D00AC23F1 /* image-4@2x.png in Resources */, + DAA1762319D89B600044227B /* thumb_touch_id@3x.png in Resources */, + DAA1764E19D8B82B0044227B /* initial.png in Resources */, DABD39481711E29700CF925C /* avatar-17.png in Resources */, DABD39491711E29700CF925C /* avatar-17@2x.png in Resources */, DA25C5FD197CCAF70046CDCF /* icon_list-names@2x.png in Resources */, DAC8DF47192831E100BA7D71 /* icon_key.png in Resources */, - DA250A071956484D00AC23F1 /* image-5@2x.png in Resources */, DAC8DF48192831E100BA7D71 /* icon_key@2x.png in Resources */, DABD394A1711E29700CF925C /* avatar-18.png in Resources */, + DAA1764919D8B82B0044227B /* login_new@2x.png in Resources */, DABD394B1711E29700CF925C /* avatar-18@2x.png in Resources */, DABD394C1711E29700CF925C /* avatar-1@2x.png in Resources */, DA32D07A19D7D784004F3F0E /* background@3x.png in Resources */, DA32D04319D27093004F3F0E /* thumb_generated_answers@2x.png in Resources */, DA29993319C9214600AF7DF1 /* icon_star-hollow.png in Resources */, + DAA1764D19D8B82B0044227B /* initial@2x.png in Resources */, DA29993019C86F5700AF7DF1 /* thumb_generated_login.png in Resources */, + DAA1764319D8B82B0044227B /* personal_pw@2x.png in Resources */, + DAA1764A19D8B82B0044227B /* login_new.png in Resources */, DA071BF3190187FE00179766 /* empty@2x.png in Resources */, DA32D04219D27093004F3F0E /* thumb_generated_answers@3x.png in Resources */, + DAA1764119D8B82B0044227B /* settings@2x.png in Resources */, DA67460E18DE7F0C00DFE240 /* Exo2.0-Regular.otf in Resources */, + DAA1763F19D8B82B0044227B /* site_new@2x.png in Resources */, DABD394D1711E29700CF925C /* avatar-2.png in Resources */, DABD394E1711E29700CF925C /* avatar-2@2x.png in Resources */, - DA250A061956484D00AC23F1 /* image-6.png in Resources */, DA32D04A19D2F417004F3F0E /* thumb_fuel.png in Resources */, DABD394F1711E29700CF925C /* avatar-3.png in Resources */, DA67460F18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf in Resources */, + DAA1765019D8B82B0044227B /* counter.png in Resources */, DABD39501711E29700CF925C /* avatar-3@2x.png in Resources */, + DAA1764719D8B82B0044227B /* mpw_new@2x.png in Resources */, DA25C600197DBF260046CDCF /* icon_trash.png in Resources */, DA32D05219D3D107004F3F0E /* icon_meter@2x.png in Resources */, DABD39511711E29700CF925C /* avatar-4.png in Resources */, - DA2509FD1956484D00AC23F1 /* image-10@2x.png in Resources */, DABD39521711E29700CF925C /* avatar-4@2x.png in Resources */, DABD39531711E29700CF925C /* avatar-5.png in Resources */, DA73049E194E022700E72520 /* ui_spinner@2x.png in Resources */, DABD39541711E29700CF925C /* avatar-5@2x.png in Resources */, DA32D05019D2F59B004F3F0E /* meter_fuel.png in Resources */, - DA250A031956484D00AC23F1 /* image-7@2x.png in Resources */, DA25C5FA197CCAE00046CDCF /* icon_delete.png in Resources */, DA25C601197DBF260046CDCF /* icon_trash@2x.png in Resources */, DABD39551711E29700CF925C /* avatar-6.png in Resources */, + DAA1764C19D8B82B0044227B /* login_name.png in Resources */, DA32D00919CF5C55004F3F0E /* icon_question.png in Resources */, DABD39561711E29700CF925C /* avatar-6@2x.png in Resources */, DA32D07B19D7D784004F3F0E /* background@2x.png in Resources */, DABD39571711E29700CF925C /* avatar-7.png in Resources */, DABD39581711E29700CF925C /* avatar-7@2x.png in Resources */, DABD39591711E29700CF925C /* avatar-8.png in Resources */, - DA250A0D1956484D00AC23F1 /* image-2@2x.png in Resources */, DA32D00A19CF5C55004F3F0E /* icon_question@2x.png in Resources */, - DA250A051956484D00AC23F1 /* image-6@2x.png in Resources */, + DAA1764019D8B82B0044227B /* site_new.png in Resources */, DABD395A1711E29700CF925C /* avatar-8@2x.png in Resources */, DABD395B1711E29700CF925C /* avatar-9.png in Resources */, + DAA1765419D8B82B0044227B /* choose_type.png in Resources */, DABD395C1711E29700CF925C /* avatar-9@2x.png in Resources */, + DAA1762519D89B610044227B /* thumb_touch_id.png in Resources */, DA45224719062899008F650A /* icon_settings.png in Resources */, DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */, DA32D04E19D2F59B004F3F0E /* meter_fuel@3x.png in Resources */, - DA250A101956484D00AC23F1 /* image-1.png in Resources */, DA25C5FE197DBF200046CDCF /* icon_thumbs-up.png in Resources */, DABD39871711E29700CF925C /* SourceCodePro-Black.otf in Resources */, - DA2509FE1956484D00AC23F1 /* image-10.png in Resources */, DABD39881711E29700CF925C /* SourceCodePro-ExtraLight.otf in Resources */, DABD39A01711E29700CF925C /* icon_action.png in Resources */, DABD39A11711E29700CF925C /* icon_action@2x.png in Resources */, @@ -3210,28 +3318,27 @@ DA29992F19C86F5700AF7DF1 /* thumb_generated_login@2x.png in Resources */, DA73049F194E022B00E72520 /* ui_textfield.png in Resources */, DABD39F31711E29700CF925C /* icon_cancel@2x.png in Resources */, - DA250A0C1956484D00AC23F1 /* image-3.png in Resources */, DABD3A261711E29700CF925C /* icon_edit.png in Resources */, DABD3A271711E29700CF925C /* icon_edit@2x.png in Resources */, DABD3A3A1711E29700CF925C /* icon_find.png in Resources */, DABD3A3B1711E29700CF925C /* icon_find@2x.png in Resources */, + DAA1765319D8B82B0044227B /* choose_type@2x.png in Resources */, DABD3AA01711E29800CF925C /* icon_pause.png in Resources */, DABD3AA11711E29800CF925C /* icon_pause@2x.png in Resources */, + DAA1764219D8B82B0044227B /* settings.png in Resources */, DABD3AAA1711E29800CF925C /* icon_person.png in Resources */, + DAA1762719D89B610044227B /* thumb_ios_integration@2x.png in Resources */, DABD3AAB1711E29800CF925C /* icon_person@2x.png in Resources */, DABD3ABC1711E29800CF925C /* icon_play.png in Resources */, DABD3ABD1711E29800CF925C /* icon_play@2x.png in Resources */, DABD3ABE1711E29800CF925C /* icon_plus.png in Resources */, - DA250A081956484D00AC23F1 /* image-5.png in Resources */, DABD3ABF1711E29800CF925C /* icon_plus@2x.png in Resources */, DA69540717D975D900BF294E /* icon_gears@2x.png in Resources */, DABD3B1C1711E29800CF925C /* icon_up.png in Resources */, DA32D04419D27093004F3F0E /* thumb_generated_answers.png in Resources */, DABD3B1D1711E29800CF925C /* icon_up@2x.png in Resources */, DA3BCFCB19BD09D5006B2681 /* SourceCodePro-Regular.otf in Resources */, - DA250A121956484D00AC23F1 /* image-0.png in Resources */, DA4522441902355C008F650A /* icon_book.png in Resources */, - DA2509FF1956484D00AC23F1 /* image-9@2x.png in Resources */, DABD3B8D1711E29800CF925C /* keypad.png in Resources */, DABD3B8E1711E29800CF925C /* logo-bare.png in Resources */, DA7304E6194E025900E72520 /* tip_basic_black@2x.png in Resources */, @@ -3243,33 +3350,35 @@ DABD3B971711E29800CF925C /* pull-up.png in Resources */, DABD3B981711E29800CF925C /* pull-up@2x.png in Resources */, DA7304A0194E022B00E72520 /* ui_textfield@2x.png in Resources */, + DAA1765119D8B82B0044227B /* copy_pw@2x.png in Resources */, DA32D04819D2F417004F3F0E /* thumb_fuel@3x.png in Resources */, DA452249190628A1008F650A /* icon_wrench.png in Resources */, + DAA1764519D8B82B0044227B /* name_new@2x.png in Resources */, DA45224819062899008F650A /* icon_settings@2x.png in Resources */, - DA250A001956484D00AC23F1 /* image-9.png in Resources */, DA854C8418D4CFBF00106317 /* avatar-add.png in Resources */, + DAA1764B19D8B82B0044227B /* login_name@2x.png in Resources */, DABD3C241711E2DC00CF925C /* MasterPassword.entitlements in Resources */, DABD3C251711E2DC00CF925C /* Settings.bundle in Resources */, DABD3C261711E2DC00CF925C /* InfoPlist.strings in Resources */, DA32D05119D3D107004F3F0E /* icon_meter.png in Resources */, + DAA1765219D8B82B0044227B /* copy_pw.png in Resources */, DA25C5F8197AFFB40046CDCF /* icon_tools.png in Resources */, - DA250A0B1956484D00AC23F1 /* image-3@2x.png in Resources */, DABD3FCA1712446200CF925C /* cloud.png in Resources */, DABD3FCB1712446200CF925C /* cloud@2x.png in Resources */, DABD3FCE1714F45C00CF925C /* identity.png in Resources */, + DAA1762619D89B610044227B /* thumb_ios_integration@3x.png in Resources */, DABD3FCF1714F45C00CF925C /* identity@2x.png in Resources */, + DAA1764619D8B82B0044227B /* name_new.png in Resources */, DA45224B190628B2008F650A /* icon_gear.png in Resources */, DA25C5FF197DBF200046CDCF /* icon_thumbs-up@2x.png in Resources */, DAE1EF2217E942DE00BC0086 /* Localizable.strings in Resources */, DA38D6A318CCB5BF009AEB3E /* Storyboard.storyboard in Resources */, - DA250A021956484D00AC23F1 /* image-8.png in Resources */, DA5A09DF171A70E4005284AB /* play.png in Resources */, DA5A09E0171A70E4005284AB /* play@2x.png in Resources */, DA5A09EA171BB0F7005284AB /* unlocked.png in Resources */, - DA250A0E1956484D00AC23F1 /* image-2.png in Resources */, + DAA1762819D89B610044227B /* thumb_ios_integration.png in Resources */, DA5A09EB171BB0F7005284AB /* unlocked@2x.png in Resources */, - DA250A011956484D00AC23F1 /* image-8@2x.png in Resources */, - DA250A111956484D00AC23F1 /* image-0@2x.png in Resources */, + DAA1764F19D8B82B0044227B /* counter@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3363,6 +3472,19 @@ 93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */, 93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */, 93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */, + 93D392FD5E2052F7D7DB3774 /* NSString+MPMarkDown.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DAA1757919D86BE70044227B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DAA175F519D86C620044227B /* markdown_lib.m in Sources */, + DAA1761019D86C620044227B /* README_PEG-MARKDWON.markdown in Sources */, + DAA1760F19D86C620044227B /* README.markdown in Sources */, + DAA175F719D86C620044227B /* markdown_parser.m in Sources */, + DAA175F619D86C620044227B /* markdown_output.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3884,6 +4006,30 @@ }; name = "AppStore-iOS"; }; + DAA1758C19D86BE80044227B /* Debug-iOS */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_PREFIX_HEADER = "../../../External/AttributedMarkdown/attributed-markdown.pch"; + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + }; + name = "Debug-iOS"; + }; + DAA1758D19D86BE80044227B /* AdHoc-iOS */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_PREFIX_HEADER = "../../../External/AttributedMarkdown/attributed-markdown.pch"; + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + }; + name = "AdHoc-iOS"; + }; + DAA1758E19D86BE80044227B /* AppStore-iOS */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_PREFIX_HEADER = "../../../External/AttributedMarkdown/attributed-markdown.pch"; + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + }; + name = "AppStore-iOS"; + }; DAC632661486805C0075AEA5 /* Debug-iOS */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4013,6 +4159,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "AdHoc-iOS"; }; + DAA1758B19D86BE80044227B /* Build configuration list for PBXNativeTarget "AttributedMarkdown" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DAA1758C19D86BE80044227B /* Debug-iOS */, + DAA1758D19D86BE80044227B /* AdHoc-iOS */, + DAA1758E19D86BE80044227B /* AppStore-iOS */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "AdHoc-iOS"; + }; DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/MasterPassword/ObjC/iOS/NSString+MPMarkDown.h b/MasterPassword/ObjC/iOS/NSString+MPMarkDown.h new file mode 100644 index 00000000..812cbf7b --- /dev/null +++ b/MasterPassword/ObjC/iOS/NSString+MPMarkDown.h @@ -0,0 +1,25 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// NSString(MPMarkDown).h +// NSString(MPMarkDown) +// +// Created by lhunath on 2014-09-28. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import + +@interface NSString(MPMarkDown) + +- (NSAttributedString *)attributedMarkdownStringWithFontSize:(CGFloat)fontSize; + +@end diff --git a/MasterPassword/ObjC/iOS/NSString+MPMarkDown.m b/MasterPassword/ObjC/iOS/NSString+MPMarkDown.m new file mode 100644 index 00000000..41fad7e7 --- /dev/null +++ b/MasterPassword/ObjC/iOS/NSString+MPMarkDown.m @@ -0,0 +1,56 @@ +/** +* 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 +*/ + +// +// NSString(MPMarkDown).h +// NSString(MPMarkDown) +// +// Created by lhunath on 2014-09-28. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "NSString+MPMarkDown.h" +#import "markdown_lib.h" +#import "markdown_peg.h" + +@implementation NSString(MPMarkDown) + +- (NSAttributedString *)attributedMarkdownStringWithFontSize:(CGFloat)fontSize { + + NSMutableAttributedString *attributedString = markdown_to_attr_string( self, 0, @{ + @(EMPH) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Bold" size:fontSize] }, + @(STRONG) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-ExtraBold" size:fontSize] }, + @(EMPH | STRONG) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-ExtraBold" size:fontSize] }, + @(PLAIN) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Regular" size:fontSize] }, + @(H1) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 2.f] }, + @(H2) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.5f] }, + @(H3) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.17f] }, + @(H4) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.f] }, + @(H5) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * .83f] }, + @(H6) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * .75f] }, + @(BLOCKQUOTE) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.17f] }, + @(CODE) : @{ NSFontAttributeName : [UIFont fontWithName:@"SourceCodePro-Regular" size:fontSize] }, + @(VERBATIM) : @{ NSFontAttributeName : [UIFont fontWithName:@"SourceCodePro-Regular" size:fontSize] }, + @(NOTE) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.17f] }, + } ); + + // Trim trailing newlines. + NSCharacterSet *trimSet = [NSCharacterSet newlineCharacterSet]; + while (YES) { + NSRange range = [attributedString.string rangeOfCharacterFromSet:trimSet options:NSBackwardsSearch]; + if (!range.length || NSMaxRange( range ) != attributedString.length) + break; + + [attributedString replaceCharactersInRange:range withString:@""]; + } + + return attributedString; +} +@end diff --git a/MasterPassword/ObjC/iOS/Storyboard.storyboard b/MasterPassword/ObjC/iOS/Storyboard.storyboard index 8edb8b31..76eba53d 100644 --- a/MasterPassword/ObjC/iOS/Storyboard.storyboard +++ b/MasterPassword/ObjC/iOS/Storyboard.storyboard @@ -5,6 +5,8 @@ + + @@ -68,6 +70,11 @@ Exo2.0-Regular Exo2.0-Regular Exo2.0-Regular + Exo2.0-Regular + Exo2.0-Regular + Exo2.0-Regular + Exo2.0-Regular + Exo2.0-Regular Exo2.0-Thin @@ -130,6 +137,9 @@ + @@ -137,7 +147,7 @@ - + @@ -147,14 +157,14 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - + + + @@ -427,6 +496,7 @@ + @@ -434,12 +504,13 @@ - + + @@ -1521,6 +1592,35 @@ + + + + + + + + + + + + + + + + + + + + + @@ -1580,6 +1680,8 @@ + + @@ -1605,6 +1707,7 @@ + @@ -1624,7 +1727,7 @@ - + @@ -1633,228 +1736,224 @@ - + - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - + @@ -1880,6 +1979,7 @@ + @@ -1973,10 +2073,10 @@ - + - + @@ -2016,13 +2116,13 @@