From 2e5cbac76110f668e257d8f2683dff896bf37a89 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Wed, 17 Sep 2014 01:34:58 -0400 Subject: [PATCH] Added in-app purchase store and made generated logins a product. --- External/Pearl | 2 +- MasterPassword/ObjC/MPAlgorithmV0.m | 3 +- MasterPassword/ObjC/MPAppDelegate_Shared.h | 2 + MasterPassword/ObjC/MPAppDelegate_Shared.m | 5 + MasterPassword/ObjC/MPTypes.h | 2 + MasterPassword/ObjC/iOS/MPPasswordCell.m | 4 + .../ObjC/iOS/MPPasswordsViewController.m | 2 +- .../ObjC/iOS/MPStoreViewController.h | 29 ++ .../ObjC/iOS/MPStoreViewController.m | 181 +++++++++ MasterPassword/ObjC/iOS/MPiOSAppDelegate.h | 5 + MasterPassword/ObjC/iOS/MPiOSAppDelegate.m | 56 ++- .../project.pbxproj | 28 +- MasterPassword/ObjC/iOS/Storyboard.storyboard | 373 +++++++++++++++++- .../Resources/Media/thumb_generated_login.png | Bin 0 -> 32891 bytes .../Media/thumb_generated_login@2x.png | Bin 0 -> 107694 bytes .../Media/thumb_generated_login@3x.png | Bin 0 -> 229539 bytes 16 files changed, 681 insertions(+), 11 deletions(-) create mode 100644 MasterPassword/ObjC/iOS/MPStoreViewController.h create mode 100644 MasterPassword/ObjC/iOS/MPStoreViewController.m create mode 100644 MasterPassword/Resources/Media/thumb_generated_login.png create mode 100644 MasterPassword/Resources/Media/thumb_generated_login@2x.png create mode 100644 MasterPassword/Resources/Media/thumb_generated_login@3x.png diff --git a/External/Pearl b/External/Pearl index b63670d8..baba3dbd 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit b63670d86d557bf37052c2c5ea0af6387731e5bb +Subproject commit baba3dbddc87937b176f27f643085180f206207e diff --git a/MasterPassword/ObjC/MPAlgorithmV0.m b/MasterPassword/ObjC/MPAlgorithmV0.m index 8ad590a1..ced1ae4c 100644 --- a/MasterPassword/ObjC/MPAlgorithmV0.m +++ b/MasterPassword/ObjC/MPAlgorithmV0.m @@ -17,6 +17,7 @@ #import "MPAlgorithmV0.h" #import "MPEntities.h" +#import "MPAppDelegate_Shared.h" #include #include @@ -501,7 +502,7 @@ NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." ); NSString *name = element.name; - BOOL loginGenerated = element.loginGenerated; + BOOL loginGenerated = element.loginGenerated && [[MPAppDelegate_Shared get] isPurchased:MPProductGenerateLogins]; NSString *loginName = loginGenerated? nil: element.loginName; id algorithm = nil; if (!name.length) diff --git a/MasterPassword/ObjC/MPAppDelegate_Shared.h b/MasterPassword/ObjC/MPAppDelegate_Shared.h index 96e2f0ff..0689ebc6 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Shared.h +++ b/MasterPassword/ObjC/MPAppDelegate_Shared.h @@ -24,4 +24,6 @@ - (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context; - (void)setActiveUser:(MPUserEntity *)activeUser; +- (BOOL)isPurchased:(NSString *)productIdentifier; + @end diff --git a/MasterPassword/ObjC/MPAppDelegate_Shared.m b/MasterPassword/ObjC/MPAppDelegate_Shared.m index cdfcfe02..7d4e7227 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Shared.m +++ b/MasterPassword/ObjC/MPAppDelegate_Shared.m @@ -50,4 +50,9 @@ self.activeUserOID = activeUser.objectID; } +- (BOOL)isPurchased:(NSString *)productIdentifier { + + return [[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier] != nil; +} + @end diff --git a/MasterPassword/ObjC/MPTypes.h b/MasterPassword/ObjC/MPTypes.h index b1d127fd..1954b71c 100644 --- a/MasterPassword/ObjC/MPTypes.h +++ b/MasterPassword/ObjC/MPTypes.h @@ -84,6 +84,8 @@ typedef NS_ENUM(NSUInteger, MPElementType) { #define MPSitesImportedNotificationUserKey @"MPSitesImportedNotificationUserKey" #define MPInconsistenciesFixResultUserKey @"MPInconsistenciesFixResultUserKey" +#define MPProductGenerateLogins @"MPProductGenerateLogins" + static void MPCheckpoint(NSString *checkpoint, NSDictionary *attributes) { inf(@"%@: %@", checkpoint, attributes); diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.m b/MasterPassword/ObjC/iOS/MPPasswordCell.m index 66665822..dfffe6dd 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.m +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.m @@ -478,6 +478,10 @@ [self.loginNameField resignFirstResponder]; [self.passwordField resignFirstResponder]; } + if ([[MPiOSAppDelegate get] isPurchased:MPProductGenerateLogins]) + [self.loginNameButton setTitle:@"Tap to generate username or use pencil to save one" forState:UIControlStateNormal]; + else + [self.loginNameButton setTitle:@"Tap the pencil to save a username" forState:UIControlStateNormal]; // Site Name self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainElement.name, diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m index 4a7cf778..3f7a574e 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m @@ -389,7 +389,7 @@ referenceSizeForHeaderInSection:(NSInteger)section { [self.passwordCollectionView performBatchUpdates:^{ [self fetchedItemsDidUpdate]; - NSInteger fromSections = self.passwordCollectionView.numberOfSections; + NSInteger fromSections = [oldSections count]; NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView]; for (NSInteger section = 0; section < MAX( toSections, fromSections ); ++section) { if (section >= fromSections) diff --git a/MasterPassword/ObjC/iOS/MPStoreViewController.h b/MasterPassword/ObjC/iOS/MPStoreViewController.h new file mode 100644 index 00000000..039f6a9d --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPStoreViewController.h @@ -0,0 +1,29 @@ +// +// MPPreferencesViewController.h +// MasterPassword-iOS +// +// Created by Maarten Billemont on 04/06/12. +// Copyright (c) 2012 Lyndir. All rights reserved. +// + +#import +#import "MPTypeViewController.h" +@class MPStoreProductCell; + +@interface MPStoreViewController : PearlMutableStaticTableViewController + +@property(weak, nonatomic) IBOutlet MPStoreProductCell *generateLoginCell; +@property(weak, nonatomic) IBOutlet MPStoreProductCell *generateAnswersCell; +@property(weak, nonatomic) IBOutlet MPStoreProductCell *advancedExportCell; +@property(weak, nonatomic) IBOutlet MPStoreProductCell *iOSIntegrationCell; +@property(weak, nonatomic) IBOutlet MPStoreProductCell *touchIDCell; + +@end + +@interface MPStoreProductCell : UITableViewCell + +@property(nonatomic) IBOutlet UILabel *priceLabel; +@property(nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; +@property(nonatomic) IBOutlet UIView *purchasedIndicator; + +@end diff --git a/MasterPassword/ObjC/iOS/MPStoreViewController.m b/MasterPassword/ObjC/iOS/MPStoreViewController.m new file mode 100644 index 00000000..c3f10482 --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPStoreViewController.m @@ -0,0 +1,181 @@ +// +// MPPreferencesViewController.m +// MasterPassword-iOS +// +// Created by Maarten Billemont on 04/06/12. +// Copyright (c) 2012 Lyndir. All rights reserved. +// + +#import +#import "MPStoreViewController.h" +#import "MPiOSAppDelegate.h" +#import "MPAppDelegate_Key.h" +#import "MPAppDelegate_Store.h" +#import "UIColor+Expanded.h" +#import "MPPasswordsViewController.h" +#import "MPCoachmarkViewController.h" + +@interface MPStoreViewController() + +@property(nonatomic, strong) NSNumberFormatter *currencyFormatter; + +@end + +@implementation MPStoreViewController + +- (void)viewDidLoad { + + [super viewDidLoad]; + + self.currencyFormatter = [NSNumberFormatter new]; + self.currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + + self.view.backgroundColor = [UIColor clearColor]; +} + +- (void)viewWillAppear:(BOOL)animated { + + [super viewWillAppear:animated]; + + self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 ); + + [self reloadCellsHiding:self.allCellsBySection[0] showing:nil]; + [self.allCellsBySection[0] enumerateObjectsUsingBlock:^(MPStoreProductCell *cell, NSUInteger idx, BOOL *stop) { + if ([cell isKindOfClass:[MPStoreProductCell class]]) { + cell.purchasedIndicator.alpha = 0; + [cell.activityIndicator stopAnimating]; + } + }]; + + [[MPiOSAppDelegate get] observeKeyPath:@"products" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) { + if (NSNullToNil( to )) + PearlMainQueue( ^{ + [self updateWithProducts:to]; + } ); + }]; + [[MPiOSAppDelegate get] observeKeyPath:@"productTransactions" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) { + if (NSNullToNil( to )) + PearlMainQueue( ^{ + [self updateWithTransactions:to]; + } ); + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + [self updateWithProducts:[MPiOSAppDelegate get].products]; + }]; +} + +#pragma mark - UITableViewDelegate + +- (MPStoreProductCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + MPStoreProductCell *cell = (MPStoreProductCell *)[super tableView:tableView cellForRowAtIndexPath:indexPath]; + if (cell.selectionStyle != UITableViewCellSelectionStyleNone) { + cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds]; + cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRGBAHex:0x78DDFB33]; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + + MPStoreProductCell *cell = (MPStoreProductCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath]; + SKProduct *product = [self productForCell:cell]; + + if (product && [SKPaymentQueue canMakePayments]) { + SKPayment *payment = nil; + if (cell == self.generateLoginCell) + payment = [SKPayment paymentWithProduct:product]; + if (cell == self.generateAnswersCell) { + } + if (cell == self.advancedExportCell) { + } + if (cell == self.iOSIntegrationCell) { + } + if (cell == self.touchIDCell) { + } + + [[SKPaymentQueue defaultQueue] addPayment:payment]; + } + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +#pragma mark - Private + +- (SKProduct *)productForCell:(MPStoreProductCell *)cell { + + for (SKProduct *product in [MPiOSAppDelegate get].products) + if ([self cellForProductIdentifier:product.productIdentifier] == cell) + return product; + + return nil; +} + +- (MPStoreProductCell *)cellForProductIdentifier:(NSString *)productIdentifier { + + if ([productIdentifier isEqualToString:MPProductGenerateLogins]) + return self.generateLoginCell; + + return nil; +} + +- (void)updateWithProducts:(NSArray *)products { + + NSMutableArray *showCells = [NSMutableArray array]; + NSMutableArray *hideCells = [NSMutableArray array]; + [hideCells addObjectsFromArray:self.allCellsBySection[0]]; + + for (SKProduct *product in products) { + [self showCell:self.generateLoginCell ifProduct:product hasProductIdentifier:MPProductGenerateLogins showingCells:showCells]; + } + + [hideCells removeObjectsInArray:showCells]; + [self updateCellsHiding:hideCells showing:showCells animation:UITableViewRowAnimationAutomatic]; +} + +- (void)showCell:(MPStoreProductCell *)cell ifProduct:(SKProduct *)product hasProductIdentifier:(NSString *)productIdentifier + showingCells:(NSMutableArray *)showCells { + + if (![product.productIdentifier isEqualToString:productIdentifier]) + return; + + [showCells addObject:cell]; + + self.currencyFormatter.locale = product.priceLocale; + cell.priceLabel.text = [self.currencyFormatter stringFromNumber:product.price]; + cell.purchasedIndicator.alpha = [[MPiOSAppDelegate get] isPurchased:productIdentifier]? 1: 0; +} + +- (void)updateWithTransactions:(NSArray *)transactions { + + for (SKPaymentTransaction *transaction in transactions) { + MPStoreProductCell *cell = [self cellForProductIdentifier:transaction.payment.productIdentifier]; + if (!cell) + continue; + + switch (transaction.transactionState) { + case SKPaymentTransactionStatePurchasing: + [cell.activityIndicator startAnimating]; + break; + case SKPaymentTransactionStatePurchased: + [cell.activityIndicator stopAnimating]; + break; + case SKPaymentTransactionStateFailed: + [cell.activityIndicator stopAnimating]; + break; + case SKPaymentTransactionStateRestored: + [cell.activityIndicator stopAnimating]; + break; + case SKPaymentTransactionStateDeferred: + [cell.activityIndicator startAnimating]; + break; + } + } +} + +@end + +@implementation MPStoreProductCell +@end diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.h b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.h index abeb98b5..345766c6 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.h +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.h @@ -8,11 +8,16 @@ #import #import +#import #import "MPAppDelegate_Shared.h" @interface MPiOSAppDelegate : MPAppDelegate_Shared +@property(nonatomic, strong) NSArray /* SKProduct */ *products; + +@property(nonatomic, strong) NSArray *productTransactions; + - (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController; - (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController; diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index 5cba92dc..5e78b338 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -6,12 +6,13 @@ // Copyright (c) 2011 Lyndir. All rights reserved. // +#import #import "MPiOSAppDelegate.h" #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" #import "IASKSettingsReader.h" -@interface MPiOSAppDelegate() +@interface MPiOSAppDelegate() @property(nonatomic, weak) PearlAlert *handleCloudDisabledAlert; @property(nonatomic, weak) PearlAlert *handleCloudContentAlert; @@ -23,14 +24,15 @@ + (void)initialize { - if ([self class] == [MPiOSAppDelegate class]) { + static dispatch_once_t once = 0; + dispatch_once( &once, ^{ [PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo; #ifdef DEBUG [PearlLogger get].printLevel = PearlLogLevelDebug; //Trace; #else [PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo; #endif - } + } ); } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { @@ -146,6 +148,12 @@ @"legal" : @"YES", #endif } ); + + [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; + SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[[NSSet alloc] initWithObjects: + MPProductGenerateLogins, nil]]; + productsRequest.delegate = self; + [productsRequest start]; } @catch (id exception) { err( @"During Post-Startup: %@", exception ); @@ -274,6 +282,48 @@ [super applicationDidBecomeActive:application]; } +#pragma mark - SKProductsRequestDelegate + +- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { + + inf( @"products: %@, invalid: %@", response.products, response.invalidProductIdentifiers ); + self.products = response.products; +} + +- (void)request:(SKRequest *)request didFailWithError:(NSError *)error { + + err( @"StoreKit request (%@) failed: %@", request, error ); +} + +#pragma mark - SKPaymentTransactionObserver + +- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { + + for (SKPaymentTransaction *transaction in transactions) { + dbg( @"transaction updated: %@", transaction ); + switch (transaction.transactionState) { + case SKPaymentTransactionStatePurchased: + case SKPaymentTransactionStateRestored: { + inf( @"purchased: %@", transaction.payment.productIdentifier ); + [[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier + forKey:transaction.payment.productIdentifier]; + break; + } + case SKPaymentTransactionStatePurchasing: + case SKPaymentTransactionStateFailed: + case SKPaymentTransactionStateDeferred: + break; + } + } + + self.productTransactions = transactions; +} + +- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { + + err( @"StoreKit restore failed: %@", error ); +} + #pragma mark - Behavior - (void)showReview { diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index 5443fdb7..69083e0b 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; }; 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 */; }; 93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */; }; 93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */; }; 93D394B5036C882B33C71872 /* MPPasswordsSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */; }; @@ -90,6 +91,11 @@ DA25C601197DBF260046CDCF /* icon_trash@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38511711E29700CF925C /* icon_trash@2x.png */; }; DA25C6B41980D3C50046CDCF /* openssl in Headers */ = {isa = PBXBuildFile; fileRef = DA25C6B31980D3C10046CDCF /* openssl */; settings = {ATTRIBUTES = (Public, ); }; }; DA25C6B61980D3DF0046CDCF /* scrypt in Headers */ = {isa = PBXBuildFile; fileRef = DA25C6B51980D3DD0046CDCF /* scrypt */; settings = {ATTRIBUTES = (Public, ); }; }; + DA29992F19C86F5700AF7DF1 /* thumb_generated_login@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA29992D19C86F5700AF7DF1 /* thumb_generated_login@2x.png */; }; + DA29993019C86F5700AF7DF1 /* thumb_generated_login.png in Resources */ = {isa = PBXBuildFile; fileRef = DA29992E19C86F5700AF7DF1 /* thumb_generated_login.png */; }; + DA29993219C9132F00AF7DF1 /* thumb_generated_login@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA29993119C9132F00AF7DF1 /* thumb_generated_login@3x.png */; }; + DA29993319C9214600AF7DF1 /* icon_star-hollow.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD382A1711E29600CF925C /* icon_star-hollow.png */; }; + DA29993419C9214600AF7DF1 /* icon_star-hollow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD382B1711E29600CF925C /* icon_star-hollow@2x.png */; }; DA2CA4DD18D28859007798F8 /* NSArray+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4D918D28859007798F8 /* NSArray+Pearl.m */; }; DA2CA4DE18D28859007798F8 /* NSArray+Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4DA18D28859007798F8 /* NSArray+Pearl.h */; }; DA2CA4DF18D28859007798F8 /* NSTimer+PearlBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4DB18D28859007798F8 /* NSTimer+PearlBlock.m */; }; @@ -273,6 +279,8 @@ DADB4ECD19C66FB60065A78D /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DADB4ECC19C66FB60065A78D /* MPElementGeneratedEntity.m */; }; DADB4ED019C66FB70065A78D /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DADB4ECF19C66FB70065A78D /* MPElementStoredEntity.m */; }; DAE1EF2217E942DE00BC0086 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAE1EF2417E942DE00BC0086 /* Localizable.strings */; }; + DAE2725919C93B80007C5262 /* libInAppSettingsKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAFC5655172C573B00CB5CC5 /* libInAppSettingsKit.a */; }; + DAE2725A19C93B8E007C5262 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */; }; DAEBC45314F6364500987BF6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; }; DAEC85B518E3DD9A007FC0DF /* UIView+Touches.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEC85B118E3DD9A007FC0DF /* UIView+Touches.m */; }; DAEC85B618E3DD9A007FC0DF /* PearlUINavigationBar.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEC85B218E3DD9A007FC0DF /* PearlUINavigationBar.m */; }; @@ -297,7 +305,6 @@ DAFC568E172C57EC00CB5CC5 /* IASKSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC567E172C57EC00CB5CC5 /* IASKSlider.m */; }; DAFC568F172C57EC00CB5CC5 /* IASKSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5680172C57EC00CB5CC5 /* IASKSwitch.m */; }; DAFC5690172C57EC00CB5CC5 /* IASKTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5682172C57EC00CB5CC5 /* IASKTextField.m */; }; - DAFC5691172C582A00CB5CC5 /* libInAppSettingsKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAFC5655172C573B00CB5CC5 /* libInAppSettingsKit.a */; }; DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */; }; DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */; }; DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */; }; @@ -386,6 +393,7 @@ 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+PearlReloadFromArray.m"; sourceTree = ""; }; 93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = ""; }; 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+PearlAdjustInsets.m"; sourceTree = ""; }; + 93D39149A5F1F9B174D6D061 /* MPStoreViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStoreViewController.h; sourceTree = ""; }; 93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsViewController.h; sourceTree = ""; }; 93D39156E806BB78E04F78B9 /* PearlSizedTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlSizedTextView.m; sourceTree = ""; }; 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = ""; }; @@ -401,6 +409,7 @@ 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 = ""; }; 93D3956915634581E737B38C /* PearlNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlNavigationController.m; sourceTree = ""; }; + 93D3957D76F71A652716EECC /* MPStoreViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoreViewController.m; sourceTree = ""; }; 93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = ""; }; 93D3970502644794E8A027BE /* MPNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNavigationController.h; sourceTree = ""; }; 93D3971FE104BB4052484151 /* MPUsersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUsersViewController.h; sourceTree = ""; }; @@ -471,6 +480,9 @@ DA250A16195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionReusableView+PearlDequeue.h"; sourceTree = ""; }; DA25C6B31980D3C10046CDCF /* openssl */ = {isa = PBXFileReference; lastKnownFileType = folder; path = openssl; sourceTree = ""; }; DA25C6B51980D3DD0046CDCF /* scrypt */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scrypt; sourceTree = ""; }; + DA29992D19C86F5700AF7DF1 /* thumb_generated_login@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_generated_login@2x.png"; sourceTree = ""; }; + DA29992E19C86F5700AF7DF1 /* thumb_generated_login.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumb_generated_login.png; sourceTree = ""; }; + DA29993119C9132F00AF7DF1 /* thumb_generated_login@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_generated_login@3x.png"; sourceTree = ""; }; DA2CA4D918D28859007798F8 /* NSArray+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Pearl.m"; sourceTree = ""; }; DA2CA4DA18D28859007798F8 /* NSArray+Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Pearl.h"; sourceTree = ""; }; DA2CA4DB18D28859007798F8 /* NSTimer+PearlBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+PearlBlock.m"; sourceTree = ""; }; @@ -1358,7 +1370,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DAFC5691172C582A00CB5CC5 /* libInAppSettingsKit.a in Frameworks */, + DAE2725A19C93B8E007C5262 /* StoreKit.framework in Frameworks */, + DAE2725919C93B80007C5262 /* libInAppSettingsKit.a in Frameworks */, DA6701E016406BB400B61001 /* AdSupport.framework in Frameworks */, DA6701DE16406B7300B61001 /* Social.framework in Frameworks */, DA6701B816406A4100B61001 /* Accounts.framework in Frameworks */, @@ -1455,6 +1468,8 @@ DACA22121705DDC5002C6C22 /* External */, DA5BFA47147E415C00F98B1E /* Frameworks */, DA5BFA45147E415C00F98B1E /* Products */, + 93D39149A5F1F9B174D6D061 /* MPStoreViewController.h */, + 93D3957D76F71A652716EECC /* MPStoreViewController.m */, ); sourceTree = ""; }; @@ -1535,6 +1550,9 @@ DABD360D1711E29400CF925C /* Media */ = { isa = PBXGroup; children = ( + DA29993119C9132F00AF7DF1 /* thumb_generated_login@3x.png */, + DA29992D19C86F5700AF7DF1 /* thumb_generated_login@2x.png */, + DA29992E19C86F5700AF7DF1 /* thumb_generated_login.png */, DA2509B619563E1E00AC23F1 /* Guide */, DA071BF1190187FE00179766 /* empty@2x.png */, DA071BF2190187FE00179766 /* empty.png */, @@ -2983,6 +3001,7 @@ DA45224C190628B2008F650A /* icon_gear@2x.png in Resources */, DA854C8318D4CFBF00106317 /* avatar-add@2x.png in Resources */, DA45224A190628A1008F650A /* icon_wrench@2x.png in Resources */, + DA29993419C9214600AF7DF1 /* icon_star-hollow@2x.png in Resources */, DA071BF4190187FE00179766 /* empty.png in Resources */, DA69540617D975D900BF294E /* icon_gears.png in Resources */, DA67460D18DE7F0C00DFE240 /* Exo2.0-Thin.otf in Resources */, @@ -3021,6 +3040,8 @@ DABD394A1711E29700CF925C /* avatar-18.png in Resources */, DABD394B1711E29700CF925C /* avatar-18@2x.png in Resources */, DABD394C1711E29700CF925C /* avatar-1@2x.png in Resources */, + DA29993319C9214600AF7DF1 /* icon_star-hollow.png in Resources */, + DA29993019C86F5700AF7DF1 /* thumb_generated_login.png in Resources */, DA071BF3190187FE00179766 /* empty@2x.png in Resources */, DA67460E18DE7F0C00DFE240 /* Exo2.0-Regular.otf in Resources */, DABD394D1711E29700CF925C /* avatar-2.png in Resources */, @@ -3062,6 +3083,8 @@ DABD39A11711E29700CF925C /* icon_action@2x.png in Resources */, DABD39F21711E29700CF925C /* icon_cancel.png in Resources */, DA25C5FB197CCAE00046CDCF /* icon_delete@2x.png in Resources */, + DA29993219C9132F00AF7DF1 /* thumb_generated_login@3x.png in Resources */, + 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 */, @@ -3201,6 +3224,7 @@ 93D39EAA4D064193074D3021 /* MPFixable.m in Sources */, 93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */, 93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */, + 93D3939661CE37180AF7CD6A /* MPStoreViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MasterPassword/ObjC/iOS/Storyboard.storyboard b/MasterPassword/ObjC/iOS/Storyboard.storyboard index 0d40266a..7135cfb8 100644 --- a/MasterPassword/ObjC/iOS/Storyboard.storyboard +++ b/MasterPassword/ObjC/iOS/Storyboard.storyboard @@ -21,6 +21,11 @@ Exo2.0-Bold Exo2.0-Bold Exo2.0-Bold + Exo2.0-Bold + Exo2.0-Bold + Exo2.0-Bold + Exo2.0-Bold + Exo2.0-Bold Exo2.0-ExtraBold @@ -48,6 +53,16 @@ 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-Regular + Exo2.0-Regular + Exo2.0-Regular + Exo2.0-Regular + Exo2.0-Regular Exo2.0-Thin @@ -60,6 +75,12 @@ Exo2.0-Thin Exo2.0-Thin Exo2.0-Thin + Exo2.0-Thin + Exo2.0-Thin + Exo2.0-Thin + Exo2.0-Thin + Exo2.0-Thin + Exo2.0-Thin SourceCodePro-Black @@ -554,6 +575,7 @@ + @@ -1065,8 +1087,8 @@ - - + + @@ -1152,7 +1174,7 @@