diff --git a/MasterPassword/ObjC/MPAlgorithmV0.m b/MasterPassword/ObjC/MPAlgorithmV0.m index f847791f..cb8a6e7c 100644 --- a/MasterPassword/ObjC/MPAlgorithmV0.m +++ b/MasterPassword/ObjC/MPAlgorithmV0.m @@ -553,7 +553,7 @@ NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); NSString *name = site.name; - BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isPurchased:MPProductGenerateLogins]; + BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins]; NSString *loginName = loginGenerated? nil: site.loginName; id algorithm = nil; if (!name.length) diff --git a/MasterPassword/ObjC/MPAppDelegate_InApp.h b/MasterPassword/ObjC/MPAppDelegate_InApp.h index e702484c..64661cc0 100644 --- a/MasterPassword/ObjC/MPAppDelegate_InApp.h +++ b/MasterPassword/ObjC/MPAppDelegate_InApp.h @@ -6,21 +6,32 @@ // Copyright (c) 2011 Lyndir. All rights reserved. // +#import #import "MPAppDelegate_Shared.h" -#define MPProductGenerateLogins @"com.lyndir.masterpassword.products.generatelogins" -#define MPProductGenerateAnswers @"com.lyndir.masterpassword.products.generateanswers" +#define MPProductGenerateLogins @"com.lyndir.masterpassword.products.generatelogins" +#define MPProductGenerateAnswers @"com.lyndir.masterpassword.products.generateanswers" +#define MPProductFuel @"com.lyndir.masterpassword.products.fuel" + +#define MP_FUEL_HOURLY_RATE 30.f /* Tier 1 purchases/h ~> USD/h */ + +@protocol MPInAppDelegate + +- (void)updateWithProducts:(NSArray /* SKProduct */ *)products; +- (void)updateWithTransaction:(SKPaymentTransaction *)transaction; + +@end @interface MPAppDelegate_Shared(InApp) -@property(nonatomic, strong) NSArray /* SKProduct */ *products; -@property(nonatomic, strong) NSArray /* SKPaymentTransaction */ *paymentTransactions; +- (void)registerProductsObserver:(id)delegate; +- (void)removeProductsObserver:(id)delegate; -- (void)updateProducts; +- (void)reloadProducts; - (BOOL)canMakePayments; -- (BOOL)isPurchased:(NSString *)productIdentifier; +- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier; - (void)restoreCompletedTransactions; -- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier; +- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity; @end diff --git a/MasterPassword/ObjC/MPAppDelegate_InApp.m b/MasterPassword/ObjC/MPAppDelegate_InApp.m index 43d34198..2f1067fd 100644 --- a/MasterPassword/ObjC/MPAppDelegate_InApp.m +++ b/MasterPassword/ObjC/MPAppDelegate_InApp.m @@ -7,7 +7,6 @@ // #import "MPAppDelegate_InApp.h" -#import @interface MPAppDelegate_Shared(InApp_Private) @end @@ -15,12 +14,29 @@ @implementation MPAppDelegate_Shared(InApp) PearlAssociatedObjectProperty( NSArray*, Products, products ); -PearlAssociatedObjectProperty( NSArray*, PaymentTransactions, paymentTransactions ); +PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObservers ); -- (void)updateProducts { +- (void)registerProductsObserver:(id)delegate { + + if (!self.productObservers) + self.productObservers = [NSMutableArray array]; + [self.productObservers addObject:delegate]; + + if (self.products) + [delegate updateWithProducts:self.products]; + else + [self reloadProducts]; +} + +- (void)removeProductsObserver:(id)delegate { + + [self.productObservers removeObject:delegate]; +} + +- (void)reloadProducts { SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: - [[NSSet alloc] initWithObjects:MPProductGenerateLogins, MPProductGenerateAnswers, nil]]; + [[NSSet alloc] initWithObjects:MPProductGenerateLogins, MPProductGenerateAnswers, MPProductFuel, nil]]; productsRequest.delegate = self; [productsRequest start]; } @@ -40,9 +56,22 @@ PearlAssociatedObjectProperty( NSArray*, PaymentTransactions, paymentTransaction return [SKPaymentQueue canMakePayments]; } -- (BOOL)isPurchased:(NSString *)productIdentifier { +- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier { - return YES; //[[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier] != nil; + if (![productIdentifier length]) + // Missing a product. + return NO; + if ([productIdentifier isEqualToString:MPProductFuel]) + // Consumable product. + return NO; + +#if ADHOC || DEBUG + // All features are unlocked for beta / debug versions. + return YES; +#else + // Check if product is purchased. + return [[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier] != nil; +#endif } - (void)restoreCompletedTransactions { @@ -50,11 +79,13 @@ PearlAssociatedObjectProperty( NSArray*, PaymentTransactions, paymentTransaction [[self paymentQueue] restoreCompletedTransactions]; } -- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier { +- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity { for (SKProduct *product in self.products) if ([product.productIdentifier isEqualToString:productIdentifier]) { - [[self paymentQueue] addPayment:[SKPayment paymentWithProduct:product]]; + SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; + payment.quantity = quantity; + [[self paymentQueue] addPayment:payment]; return; } } @@ -65,10 +96,21 @@ PearlAssociatedObjectProperty( NSArray*, PaymentTransactions, paymentTransaction inf( @"products: %@, invalid: %@", response.products, response.invalidProductIdentifiers ); self.products = response.products; + + for (id productObserver in self.productObservers) + [productObserver updateWithProducts:self.products]; } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { +#if TARGET_OS_IPHONE + [PearlAlert showAlertWithTitle:@"Purchase Failed" message: + strf( @"%@\n\n%@", error.localizedDescription, + @"Ensure you are online and try logging out and back into iTunes from your device's Settings." ) + viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil + cancelTitle:@"OK" otherTitles:nil]; +#else +#endif err( @"StoreKit request (%@) failed: %@", request, [error fullDescription] ); } @@ -82,23 +124,39 @@ PearlAssociatedObjectProperty( NSArray*, PaymentTransactions, paymentTransaction - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { - dbg( @"transaction updated: %@", transaction ); + dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, transaction.transactionState ); switch (transaction.transactionState) { - case SKPaymentTransactionStatePurchased: - case SKPaymentTransactionStateRestored: { + case SKPaymentTransactionStatePurchased: { inf( @"purchased: %@", transaction.payment.productIdentifier ); + if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) { + float currentFuel = [[MPiOSConfig get].developmentFuel floatValue]; + float purchasedFuel = transaction.payment.quantity / MP_FUEL_HOURLY_RATE; + [MPiOSConfig get].developmentFuel = @(currentFuel + purchasedFuel); + } [[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier forKey:transaction.payment.productIdentifier]; + [queue finishTransaction:transaction]; + break; + } + case SKPaymentTransactionStateRestored: { + inf( @"restored: %@", transaction.payment.productIdentifier ); + [[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier + forKey:transaction.payment.productIdentifier]; + [queue finishTransaction:transaction]; break; } case SKPaymentTransactionStatePurchasing: - case SKPaymentTransactionStateFailed: case SKPaymentTransactionStateDeferred: break; + case SKPaymentTransactionStateFailed: + err( @"Transaction failed: %@, reason: %@", transaction.payment.productIdentifier, [transaction.error fullDescription] ); + [queue finishTransaction:transaction]; + break; } - } - self.paymentTransactions = transactions; + for (id productObserver in self.productObservers) + [productObserver updateWithTransaction:transaction]; + } } - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index 5cdc7dbe..74a00de8 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -197,7 +197,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext ^(NSNotification *note) { [self.mainManagedObjectContext saveToStore]; }]; - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:UIApp queue:[NSOperationQueue mainQueue] usingBlock: ^(NSNotification *note) { [self.mainManagedObjectContext saveToStore]; diff --git a/MasterPassword/ObjC/iOS/MPCombinedViewController.h b/MasterPassword/ObjC/iOS/MPCombinedViewController.h index 43762f46..42cc1f3e 100644 --- a/MasterPassword/ObjC/iOS/MPCombinedViewController.h +++ b/MasterPassword/ObjC/iOS/MPCombinedViewController.h @@ -16,6 +16,10 @@ // Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. // +#import "MPUsersViewController.h" +#import "MPPasswordsViewController.h" +#import "MPEmergencyViewController.h" + typedef NS_ENUM(NSUInteger, MPCombinedMode) { MPCombinedModeUserSelection, MPCombinedModePasswordSelection, @@ -23,7 +27,9 @@ typedef NS_ENUM(NSUInteger, MPCombinedMode) { @interface MPCombinedViewController : UIViewController -@property(assign, nonatomic) MPCombinedMode mode; -@property(strong, nonatomic) IBOutlet UIView *usersView; +@property(nonatomic) MPCombinedMode mode; +@property(nonatomic, weak) MPUsersViewController *usersVC; +@property(nonatomic, weak) MPPasswordsViewController *passwordsVC; +@property(nonatomic, weak) MPEmergencyViewController *emergencyVC; @end diff --git a/MasterPassword/ObjC/iOS/MPCombinedViewController.m b/MasterPassword/ObjC/iOS/MPCombinedViewController.m index dbb245e8..2f131875 100644 --- a/MasterPassword/ObjC/iOS/MPCombinedViewController.m +++ b/MasterPassword/ObjC/iOS/MPCombinedViewController.m @@ -22,15 +22,8 @@ #import "MPEmergencyViewController.h" #import "MPPasswordsSegue.h" -@interface MPCombinedViewController() - -@property(nonatomic, weak) MPUsersViewController *usersVC; -@property(nonatomic, weak) MPEmergencyViewController *emergencyVC; -@end - @implementation MPCombinedViewController { NSArray *_notificationObservers; - MPPasswordsViewController *_passwordsVC; } #pragma mark - Life @@ -40,6 +33,7 @@ [super viewDidLoad]; _mode = MPCombinedModeUserSelection; + [self performSegueWithIdentifier:@"users" sender:self]; } - (void)viewWillAppear:(BOOL)animated { @@ -128,7 +122,7 @@ switch (self.mode) { case MPCombinedModeUserSelection: { - self.usersView.userInteractionEnabled = YES; + self.usersVC.view.userInteractionEnabled = YES; [self.usersVC setActive:YES animated:animated]; if (_passwordsVC) { MPPasswordsSegue *segue = [[MPPasswordsSegue alloc] initWithIdentifier:@"passwords" source:_passwordsVC destination:self]; @@ -138,7 +132,7 @@ break; } case MPCombinedModePasswordSelection: { - self.usersView.userInteractionEnabled = NO; + self.usersVC.view.userInteractionEnabled = NO; [self.usersVC setActive:NO animated:animated]; [self performSegueWithIdentifier:@"passwords" sender:@{ @"animated" : @(animated) }]; break; diff --git a/MasterPassword/ObjC/iOS/MPOverlayViewController.m b/MasterPassword/ObjC/iOS/MPOverlayViewController.m index 891c4dd2..3e141278 100644 --- a/MasterPassword/ObjC/iOS/MPOverlayViewController.m +++ b/MasterPassword/ObjC/iOS/MPOverlayViewController.m @@ -29,12 +29,11 @@ _dismissSegueByButton = [NSMutableDictionary dictionary]; } -- (void)viewWillAppear:(BOOL)animated { +- (void)viewDidLoad { - [super viewWillAppear:animated]; + [super viewDidLoad]; - if (![self.childViewControllers count]) - [self performSegueWithIdentifier:@"root" sender:self]; + [self performSegueWithIdentifier:@"root" sender:self]; } - (UIViewController *)childViewControllerForStatusBarStyle { @@ -110,26 +109,25 @@ if (!destinationViewController.parentViewController) { // Winding [containerViewController addChildViewController:destinationViewController]; - [containerViewController setNeedsStatusBarAppearanceUpdate]; - [containerViewController addDismissButtonForSegue:self]; destinationViewController.view.frame = containerViewController.view.bounds; destinationViewController.view.translatesAutoresizingMaskIntoConstraints = YES; destinationViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [containerViewController.view addSubview:destinationViewController.view]; + [containerViewController setNeedsStatusBarAppearanceUpdate]; CGRectSetY( destinationViewController.view.frame, 100 ); destinationViewController.view.transform = CGAffineTransformMakeScale( 1.2f, 1.2f ); destinationViewController.view.alpha = 0; - [UIView transitionWithView:containerViewController.view duration:[self.identifier isEqualToString:@"root"]? 0: 0.3f + [UIView transitionWithView:containerViewController.view duration:0.3f options:UIViewAnimationOptionAllowAnimatedContent animations:^{ destinationViewController.view.transform = CGAffineTransformIdentity; CGRectSetY( destinationViewController.view.frame, 0 ); destinationViewController.view.alpha = 1; } completion:^(BOOL finished) { - if (finished) - [destinationViewController didMoveToParentViewController:containerViewController]; + [destinationViewController didMoveToParentViewController:containerViewController]; + [containerViewController setNeedsStatusBarAppearanceUpdate]; }]; } else { diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.m b/MasterPassword/ObjC/iOS/MPPasswordCell.m index 4540fe13..64f4ae0f 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.m +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.m @@ -466,7 +466,7 @@ // UI self.upgradeButton.gone = !mainSite.requiresExplicitMigration; - self.answersButton.gone = ![[MPiOSAppDelegate get] isPurchased:MPProductGenerateAnswers]; + self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers]; BOOL settingsMode = self.mode == MPPasswordCellModeSettings; self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0; self.loginNameField.textColor = [UIColor colorWithHexString:mainSite.loginGenerated? @"5E636D": @"6D5E63"]; @@ -480,7 +480,7 @@ [self.loginNameField resignFirstResponder]; [self.passwordField resignFirstResponder]; } - if ([[MPiOSAppDelegate get] isPurchased:MPProductGenerateLogins]) + if ([[MPiOSAppDelegate get] isFeatureUnlocked: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]; diff --git a/MasterPassword/ObjC/iOS/MPPasswordsSegue.m b/MasterPassword/ObjC/iOS/MPPasswordsSegue.m index 9da16166..08afc693 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsSegue.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsSegue.m @@ -42,11 +42,9 @@ passwordsVC.active = NO; UIView *passwordsView = passwordsVC.view; - passwordsView.translatesAutoresizingMaskIntoConstraints = NO; - [combinedVC.view insertSubview:passwordsView belowSubview:combinedVC.usersView]; - [combinedVC.view addConstraintsWithVisualFormats:@[ @"H:|[passwordsView]|", @"V:|[passwordsView]|" ] - options:0 metrics:nil - views:NSDictionaryOfVariableBindings( passwordsView )]; + passwordsView.frame = combinedVC.view.bounds; + passwordsView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [combinedVC.view insertSubview:passwordsView belowSubview:combinedVC.usersVC.view]; [passwordsVC setActive:YES animated:self.animated completion:^(BOOL finished) { if (!finished) diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m index d3760fd6..f710e5f4 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m @@ -286,12 +286,28 @@ referenceSizeForHeaderInSection:(NSInteger)section { Weakify( self ); _notificationObservers = @[ [[NSNotificationCenter defaultCenter] - addObserverForName:UIApplicationWillResignActiveNotification object:nil + addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Strongify( self ); self.passwordSelectionContainer.alpha = 0; }], + [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationWillEnterForegroundNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify( self ); + + [self updatePasswords]; + }], + [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationDidBecomeActiveNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify( self ); + + [UIView animateWithDuration:0.7f animations:^{ + self.passwordSelectionContainer.alpha = 1; + }]; + }], [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { @@ -301,16 +317,6 @@ referenceSizeForHeaderInSection:(NSInteger)section { self.passwordsSearchBar.text = nil; [self.passwordCollectionView reloadData]; }], - [[NSNotificationCenter defaultCenter] - addObserverForName:UIApplicationDidBecomeActiveNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - Strongify( self ); - - [self updatePasswords]; - [UIView animateWithDuration:1 animations:^{ - self.passwordSelectionContainer.alpha = 1; - }]; - }], [[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { diff --git a/MasterPassword/ObjC/iOS/MPRootSegue.h b/MasterPassword/ObjC/iOS/MPRootSegue.h new file mode 100644 index 00000000..c05b9b5f --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPRootSegue.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 + */ + +// +// MPRootSegue.h +// MPRootSegue +// +// Created by lhunath on 2014-09-26. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import + + +@interface MPRootSegue : UIStoryboardSegue + + +@end diff --git a/MasterPassword/ObjC/iOS/MPRootSegue.m b/MasterPassword/ObjC/iOS/MPRootSegue.m new file mode 100644 index 00000000..4ec3fb5b --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPRootSegue.m @@ -0,0 +1,38 @@ +/** + * 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 + */ + +// +// MPRootSegue.h +// MPRootSegue +// +// Created by lhunath on 2014-09-26. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPRootSegue.h" + + +@implementation MPRootSegue { + +} + +- (void)perform { + + UIViewController *sourceViewController = self.sourceViewController; + UIViewController *destinationViewController = self.destinationViewController; + [sourceViewController addChildViewController:destinationViewController]; + destinationViewController.view.frame = sourceViewController.view.bounds; + destinationViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [sourceViewController.view addSubview:destinationViewController.view]; + [destinationViewController didMoveToParentViewController:sourceViewController]; + [sourceViewController setNeedsStatusBarAppearanceUpdate]; +} + +@end diff --git a/MasterPassword/ObjC/iOS/MPStoreViewController.h b/MasterPassword/ObjC/iOS/MPStoreViewController.h index 285a1718..eb005277 100644 --- a/MasterPassword/ObjC/iOS/MPStoreViewController.h +++ b/MasterPassword/ObjC/iOS/MPStoreViewController.h @@ -16,6 +16,9 @@ @property(weak, nonatomic) IBOutlet MPStoreProductCell *generateAnswersCell; @property(weak, nonatomic) IBOutlet MPStoreProductCell *iOSIntegrationCell; @property(weak, nonatomic) IBOutlet MPStoreProductCell *touchIDCell; +@property(weak, nonatomic) IBOutlet MPStoreProductCell *fuelCell; +@property(weak, nonatomic) IBOutlet NSLayoutConstraint *fuelMeterConstraint; +@property(weak, nonatomic) IBOutlet UIButton *fuelSpeedButton; @end diff --git a/MasterPassword/ObjC/iOS/MPStoreViewController.m b/MasterPassword/ObjC/iOS/MPStoreViewController.m index 7381acf5..aeae342d 100644 --- a/MasterPassword/ObjC/iOS/MPStoreViewController.m +++ b/MasterPassword/ObjC/iOS/MPStoreViewController.m @@ -10,11 +10,14 @@ #import "MPiOSAppDelegate.h" #import "UIColor+Expanded.h" #import "MPAppDelegate_InApp.h" -#import -@interface MPStoreViewController() +PearlEnum( MPDevelopmentFuelConsumption, + MPDevelopmentFuelConsumptionQuarterly, MPDevelopmentFuelConsumptionMonthly, MPDevelopmentFuelWeekly ); + +@interface MPStoreViewController() @property(nonatomic, strong) NSNumberFormatter *currencyFormatter; +@property(nonatomic, strong) NSArray *products; @end @@ -47,24 +50,13 @@ } }]; - [[MPiOSAppDelegate get] observeKeyPath:@"products" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) { - if (NSNullToNil( to )) - PearlMainQueue( ^{ - [self updateWithProducts:to]; - } ); - }]; - [[MPiOSAppDelegate get] observeKeyPath:@"paymentTransactions" 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]; + [self updateProducts]; + [self updateFuel]; }]; - - [[MPiOSAppDelegate get] updateProducts]; + [[MPiOSAppDelegate get] registerProductsObserver:self]; + [self updateFuel]; } #pragma mark - UITableViewDelegate @@ -80,7 +72,7 @@ } if (indexPath.section == 0) - cell.selectionStyle = [[MPiOSAppDelegate get] isPurchased:[self productForCell:cell].productIdentifier]? + cell.selectionStyle = [[MPiOSAppDelegate get] isFeatureUnlocked:[self productForCell:cell].productIdentifier]? UITableViewCellSelectionStyleDefault: UITableViewCellSelectionStyleNone; if (cell.selectionStyle != UITableViewCellSelectionStyleNone) { @@ -113,13 +105,21 @@ SKProduct *product = [self productForCell:cell]; if (product) - [[MPAppDelegate_Shared get] purchaseProductWithIdentifier:product.productIdentifier]; + [[MPAppDelegate_Shared get] purchaseProductWithIdentifier:product.productIdentifier + quantity:[self quantityForProductIdentifier:product.productIdentifier]]; [tableView deselectRowAtIndexPath:indexPath animated:YES]; } #pragma mark - Actions +- (IBAction)toggleFuelConsumption:(id)sender { + + NSUInteger fuelConsumption = [[MPiOSConfig get].developmentFuelConsumption unsignedIntegerValue]; + [MPiOSConfig get].developmentFuelConsumption = @((fuelConsumption + 1) % MPDevelopmentFuelConsumptionCount); + [self updateProducts]; +} + - (IBAction)restorePurchases:(id)sender { [PearlAlert showAlertWithTitle:@"Restore Previous Purchases" message: @@ -133,11 +133,45 @@ } cancelTitle:@"Cancel" otherTitles:@"Find Purchases", nil]; } +#pragma mark - MPInAppDelegate + +- (void)updateWithProducts:(NSArray *)products { + + self.products = products; + + [self updateProducts]; +} + +- (void)updateWithTransaction:(SKPaymentTransaction *)transaction { + + MPStoreProductCell *cell = [self cellForProductIdentifier:transaction.payment.productIdentifier]; + if (!cell) + return; + + 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; + } +} + #pragma mark - Private - (SKProduct *)productForCell:(MPStoreProductCell *)cell { - for (SKProduct *product in [MPiOSAppDelegate get].products) + for (SKProduct *product in self.products) if ([self cellForProductIdentifier:product.productIdentifier] == cell) return product; @@ -150,19 +184,22 @@ return self.generateLoginCell; if ([productIdentifier isEqualToString:MPProductGenerateAnswers]) return self.generateAnswersCell; + if ([productIdentifier isEqualToString:MPProductFuel]) + return self.fuelCell; return nil; } -- (void)updateWithProducts:(NSArray *)products { +- (void)updateProducts { NSMutableArray *showCells = [NSMutableArray array]; NSMutableArray *hideCells = [NSMutableArray array]; [hideCells addObjectsFromArray:self.allCellsBySection[0]]; - for (SKProduct *product in products) { + for (SKProduct *product in self.products) { [self showCellForProductWithIdentifier:MPProductGenerateLogins ifProduct:product showingCells:showCells]; [self showCellForProductWithIdentifier:MPProductGenerateAnswers ifProduct:product showingCells:showCells]; + [self showCellForProductWithIdentifier:MPProductFuel ifProduct:product showingCells:showCells]; } [hideCells removeObjectsInArray:showCells]; @@ -172,6 +209,38 @@ [self updateCellsHiding:hideCells showing:showCells animation:UITableViewRowAnimationNone]; } +- (void)updateFuel { + + CGFloat weeklyFuelConsumption = [self weeklyFuelConsumption]; /* consume x fuel / week */ + CGFloat fuel = [[MPiOSConfig get].developmentFuel floatValue]; /* x fuel left */ + NSTimeInterval fuelSecondsElapsed = [[MPiOSConfig get].developmentFuelChecked timeIntervalSinceNow]; + if (fuelSecondsElapsed > 3600) { + NSTimeInterval weeksElapsed = fuelSecondsElapsed / (3600 * 24 * 7 /* 1 week */); /* x weeks elapsed */ + fuel -= weeklyFuelConsumption * weeksElapsed; + [MPiOSConfig get].developmentFuel = @(fuel); + } + + CGFloat fuelRatio = weeklyFuelConsumption == 0? 0: fuel / weeklyFuelConsumption; /* x weeks worth of fuel left */ + [self.fuelMeterConstraint updateConstant:MIN(0.5f, fuelRatio - 0.5f) * 160]; /* -80pt = 0 weeks left, 80pt = >=1 week left */ +} + +- (CGFloat)weeklyFuelConsumption { + + switch ((MPDevelopmentFuelConsumption)[[MPiOSConfig get].developmentFuelConsumption unsignedIntegerValue]) { + case MPDevelopmentFuelConsumptionQuarterly: + [self.fuelSpeedButton setTitle:@"1h / quarter" forState:UIControlStateNormal]; + return 1.f / 12 /* 12 weeks */; + case MPDevelopmentFuelConsumptionMonthly: + [self.fuelSpeedButton setTitle:@"1h / month" forState:UIControlStateNormal]; + return 1.f / 4 /* 4 weeks */; + case MPDevelopmentFuelWeekly: + [self.fuelSpeedButton setTitle:@"1h / week" forState:UIControlStateNormal]; + return 1.f; + } + + return 0; +} + - (void)showCellForProductWithIdentifier:(NSString *)productIdentifier ifProduct:(SKProduct *)product showingCells:(NSMutableArray *)showCells { @@ -182,36 +251,18 @@ [showCells addObject:cell]; self.currencyFormatter.locale = product.priceLocale; - BOOL purchased = [[MPiOSAppDelegate get] isPurchased:productIdentifier]; - cell.priceLabel.text = purchased? @"": [self.currencyFormatter stringFromNumber:product.price]; + BOOL purchased = [[MPiOSAppDelegate get] isFeatureUnlocked:productIdentifier]; + NSInteger quantity = [self quantityForProductIdentifier:productIdentifier]; + cell.priceLabel.text = purchased? @"": [self.currencyFormatter stringFromNumber:@([product.price floatValue] * quantity)]; cell.purchasedIndicator.alpha = purchased? 1: 0; } -- (void)updateWithTransactions:(NSArray *)transactions { +- (NSInteger)quantityForProductIdentifier:(NSString *)productIdentifier { - for (SKPaymentTransaction *transaction in transactions) { - MPStoreProductCell *cell = [self cellForProductIdentifier:transaction.payment.productIdentifier]; - if (!cell) - continue; + if ([productIdentifier isEqualToString:MPProductFuel]) + return (NSInteger)(MP_FUEL_HOURLY_RATE * [self weeklyFuelConsumption]); - 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; - } - } + return 1; } @end diff --git a/MasterPassword/ObjC/iOS/MPUsersViewController.m b/MasterPassword/ObjC/iOS/MPUsersViewController.m index bd5a06fe..3c0a091b 100644 --- a/MasterPassword/ObjC/iOS/MPUsersViewController.m +++ b/MasterPassword/ObjC/iOS/MPUsersViewController.m @@ -592,21 +592,25 @@ referenceSizeForFooterInSection:(NSInteger)section { Weakify( self ); _notificationObservers = @[ [[NSNotificationCenter defaultCenter] - addObserverForName:UIApplicationWillResignActiveNotification object:nil + addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Strongify( self ); -// [self emergencyCloseAnimated:NO]; self.userSelectionContainer.alpha = 0; }], + [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationWillEnterForegroundNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify( self ); + + [self reloadUsers]; + }], [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Strongify( self ); - [self reloadUsers]; - - [UIView animateWithDuration:1 animations:^{ + [UIView animateWithDuration:0.7f animations:^{ self.userSelectionContainer.alpha = 1; }]; }], diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index dcfb8ec6..cabe7a47 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -140,7 +140,7 @@ @"jailbroken" : PearlStringB( [PearlDeviceUtils isJailbroken] ), @"platform" : [PearlDeviceUtils platform], #ifdef APPSTORE - @"legal" : PearlStringB([PearlDeviceUtils isAppEncrypted]), + @"legal" : PearlStringB([PearlDeviceUtils isAppEncrypted]), #else @"legal" : @"YES", #endif @@ -256,13 +256,13 @@ [super applicationDidReceiveMemoryWarning:application]; } -- (void)applicationWillResignActive:(UIApplication *)application { +- (void)applicationDidEnterBackground:(UIApplication *)application { - inf( @"Will deactivate" ); + inf( @"Will background" ); if (![[MPiOSConfig get].rememberLogin boolValue]) [self signOutAnimated:NO]; - [super applicationWillResignActive:application]; + [super applicationDidEnterBackground:application]; } - (void)applicationDidBecomeActive:(UIApplication *)application { diff --git a/MasterPassword/ObjC/iOS/MPiOSConfig.h b/MasterPassword/ObjC/iOS/MPiOSConfig.h index f1063b08..795ca9d9 100644 --- a/MasterPassword/ObjC/iOS/MPiOSConfig.h +++ b/MasterPassword/ObjC/iOS/MPiOSConfig.h @@ -18,5 +18,8 @@ @property(nonatomic, retain) NSNumber *loginNameTipShown; @property(nonatomic, retain) NSNumber *traceMode; @property(nonatomic, retain) NSNumber *dictationSearch; +@property(nonatomic, retain) NSNumber *developmentFuel; +@property(nonatomic, retain) NSNumber *developmentFuelConsumption; +@property(nonatomic, retain) NSDate *developmentFuelChecked; @end diff --git a/MasterPassword/ObjC/iOS/MPiOSConfig.m b/MasterPassword/ObjC/iOS/MPiOSConfig.m index 05ae35fc..917578ce 100644 --- a/MasterPassword/ObjC/iOS/MPiOSConfig.m +++ b/MasterPassword/ObjC/iOS/MPiOSConfig.m @@ -9,6 +9,7 @@ @implementation MPiOSConfig @dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch; +@dynamic developmentFuel, developmentFuelConsumption, developmentFuelChecked; - (id)init { @@ -16,15 +17,15 @@ return self; [self.defaults registerDefaults:@{ - NSStringFromSelector( @selector(helpHidden) ) : @NO, - NSStringFromSelector( @selector(siteInfoHidden) ) : @YES, - NSStringFromSelector( @selector(showSetup) ) : @YES, - NSStringFromSelector( @selector(iTunesID) ) : @"510296984", - NSStringFromSelector( @selector(actionsTipShown) ) : @(!self.firstRun), - NSStringFromSelector( @selector(typeTipShown) ) : @(!self.firstRun), - NSStringFromSelector( @selector(loginNameTipShown) ) : @NO, - NSStringFromSelector( @selector(traceMode) ) : @NO, - NSStringFromSelector( @selector(dictationSearch) ) : @NO + NSStringFromSelector( @selector( helpHidden ) ) : @NO, + NSStringFromSelector( @selector( siteInfoHidden ) ) : @YES, + NSStringFromSelector( @selector( showSetup ) ) : @YES, + NSStringFromSelector( @selector( iTunesID ) ) : @"510296984", + NSStringFromSelector( @selector( actionsTipShown ) ) : @(!self.firstRun), + NSStringFromSelector( @selector( typeTipShown ) ) : @(!self.firstRun), + NSStringFromSelector( @selector( loginNameTipShown ) ) : @NO, + NSStringFromSelector( @selector( traceMode ) ) : @NO, + NSStringFromSelector( @selector( dictationSearch ) ) : @NO, }]; return self; diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index 9d1850bf..e4f8f7ba 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; }; 93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; }; 93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; }; + 93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */; }; 93D39B76DD5AB108BA8928E8 /* UIScrollView+PearlAdjustInsets.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */; }; 93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; }; 93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; }; @@ -127,6 +128,14 @@ DA32D04219D27093004F3F0E /* thumb_generated_answers@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D03F19D27093004F3F0E /* thumb_generated_answers@3x.png */; }; DA32D04319D27093004F3F0E /* thumb_generated_answers@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D04019D27093004F3F0E /* thumb_generated_answers@2x.png */; }; DA32D04419D27093004F3F0E /* thumb_generated_answers.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D04119D27093004F3F0E /* thumb_generated_answers.png */; }; + DA32D04819D2F417004F3F0E /* thumb_fuel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D04519D2F417004F3F0E /* thumb_fuel@3x.png */; }; + DA32D04919D2F417004F3F0E /* thumb_fuel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D04619D2F417004F3F0E /* thumb_fuel@2x.png */; }; + DA32D04A19D2F417004F3F0E /* thumb_fuel.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D04719D2F417004F3F0E /* thumb_fuel.png */; }; + DA32D04E19D2F59B004F3F0E /* meter_fuel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D04B19D2F59B004F3F0E /* meter_fuel@3x.png */; }; + DA32D04F19D2F59B004F3F0E /* meter_fuel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D04C19D2F59B004F3F0E /* meter_fuel@2x.png */; }; + DA32D05019D2F59B004F3F0E /* meter_fuel.png in Resources */ = {isa = PBXBuildFile; fileRef = DA32D04D19D2F59B004F3F0E /* meter_fuel.png */; }; + DA32D05119D3D107004F3F0E /* icon_meter.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37BA1711E29600CF925C /* icon_meter.png */; }; + DA32D05219D3D107004F3F0E /* icon_meter@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37BB1711E29600CF925C /* icon_meter@2x.png */; }; DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; }; DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; }; DA38D6A318CCB5BF009AEB3E /* Storyboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */; }; @@ -425,6 +434,7 @@ 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = ""; }; 93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = ""; }; 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadFromArray.h"; sourceTree = ""; }; + 93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = ""; }; 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = ""; }; 93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = ""; }; 93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = ""; }; @@ -446,6 +456,7 @@ 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlNavigationController.h; sourceTree = ""; }; 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = ""; }; 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebViewController.m; sourceTree = ""; }; + 93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRootSegue.m; sourceTree = ""; }; 93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = ""; }; 93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = ""; }; 93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = ""; }; @@ -552,6 +563,12 @@ DA32D03F19D27093004F3F0E /* thumb_generated_answers@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_generated_answers@3x.png"; sourceTree = ""; }; DA32D04019D27093004F3F0E /* thumb_generated_answers@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_generated_answers@2x.png"; sourceTree = ""; }; DA32D04119D27093004F3F0E /* thumb_generated_answers.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumb_generated_answers.png; sourceTree = ""; }; + DA32D04519D2F417004F3F0E /* thumb_fuel@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_fuel@3x.png"; sourceTree = ""; }; + DA32D04619D2F417004F3F0E /* thumb_fuel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_fuel@2x.png"; sourceTree = ""; }; + DA32D04719D2F417004F3F0E /* thumb_fuel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumb_fuel.png; sourceTree = ""; }; + DA32D04B19D2F59B004F3F0E /* meter_fuel@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "meter_fuel@3x.png"; sourceTree = ""; }; + DA32D04C19D2F59B004F3F0E /* meter_fuel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "meter_fuel@2x.png"; sourceTree = ""; }; + DA32D04D19D2F59B004F3F0E /* meter_fuel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = meter_fuel.png; sourceTree = ""; }; DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = ""; }; DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = ""; }; DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Storyboard.storyboard; sourceTree = ""; }; @@ -1525,6 +1542,8 @@ 93D3957D76F71A652716EECC /* MPStoreViewController.m */, 93D39C426E03358384018E85 /* MPAnswersViewController.m */, 93D39D6604447D7708039155 /* MPAnswersViewController.h */, + 93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */, + 93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */, ); sourceTree = ""; }; @@ -1606,6 +1625,12 @@ DABD360D1711E29400CF925C /* Media */ = { isa = PBXGroup; children = ( + DA32D04B19D2F59B004F3F0E /* meter_fuel@3x.png */, + DA32D04C19D2F59B004F3F0E /* meter_fuel@2x.png */, + DA32D04D19D2F59B004F3F0E /* meter_fuel.png */, + DA32D04519D2F417004F3F0E /* thumb_fuel@3x.png */, + DA32D04619D2F417004F3F0E /* thumb_fuel@2x.png */, + DA32D04719D2F417004F3F0E /* thumb_fuel.png */, DA32D03F19D27093004F3F0E /* thumb_generated_answers@3x.png */, DA32D04019D27093004F3F0E /* thumb_generated_answers@2x.png */, DA32D04119D27093004F3F0E /* thumb_generated_answers.png */, @@ -3086,6 +3111,7 @@ DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */, 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 */, @@ -3096,6 +3122,7 @@ DA69540617D975D900BF294E /* icon_gears.png in Resources */, DA67460D18DE7F0C00DFE240 /* Exo2.0-Thin.otf in Resources */, DA4522451902355C008F650A /* icon_book@2x.png in Resources */, + 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 */, @@ -3139,16 +3166,19 @@ 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 */, DABD39501711E29700CF925C /* avatar-3@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 */, @@ -3168,6 +3198,7 @@ DA45224719062899008F650A /* icon_settings.png in Resources */, DABD395E1711E29700CF925C /* background@2x.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 */, @@ -3214,6 +3245,7 @@ DABD3B971711E29800CF925C /* pull-up.png in Resources */, DABD3B981711E29800CF925C /* pull-up@2x.png in Resources */, DA7304A0194E022B00E72520 /* ui_textfield@2x.png in Resources */, + DA32D04819D2F417004F3F0E /* thumb_fuel@3x.png in Resources */, DA452249190628A1008F650A /* icon_wrench.png in Resources */, DA45224819062899008F650A /* icon_settings@2x.png in Resources */, DA250A001956484D00AC23F1 /* image-9.png in Resources */, @@ -3221,6 +3253,7 @@ DABD3C241711E2DC00CF925C /* MasterPassword.entitlements in Resources */, DABD3C251711E2DC00CF925C /* Settings.bundle in Resources */, DABD3C261711E2DC00CF925C /* InfoPlist.strings in Resources */, + DA32D05119D3D107004F3F0E /* icon_meter.png in Resources */, DA25C5F8197AFFB40046CDCF /* icon_tools.png in Resources */, DA250A0B1956484D00AC23F1 /* image-3@2x.png in Resources */, DABD3FCA1712446200CF925C /* cloud.png in Resources */, @@ -3331,6 +3364,7 @@ 93D390C1B93F9D3AE37DD0A5 /* MPAnswersViewController.m in Sources */, 93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */, 93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */, + 93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3959,6 +3993,7 @@ DA32D03019D111C7004F3F0E /* AppStore-iOS */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = "AdHoc-iOS"; }; DA5BFA3E147E415C00F98B1E /* Build configuration list for PBXProject "MasterPassword-iOS" */ = { isa = XCConfigurationList; diff --git a/MasterPassword/ObjC/iOS/Storyboard.storyboard b/MasterPassword/ObjC/iOS/Storyboard.storyboard index f1d5a2e6..99038c25 100644 --- a/MasterPassword/ObjC/iOS/Storyboard.storyboard +++ b/MasterPassword/ObjC/iOS/Storyboard.storyboard @@ -28,6 +28,7 @@ Exo2.0-Bold Exo2.0-Bold Exo2.0-Bold + Exo2.0-Bold Exo2.0-ExtraBold @@ -65,6 +66,8 @@ Exo2.0-Regular Exo2.0-Regular Exo2.0-Regular + Exo2.0-Regular + Exo2.0-Regular Exo2.0-Thin @@ -84,6 +87,7 @@ Exo2.0-Thin Exo2.0-Thin Exo2.0-Thin + Exo2.0-Thin SourceCodePro-Black @@ -112,11 +116,11 @@ - + - + - + - + @@ -147,7 +151,7 @@ - + - + @@ -168,7 +172,7 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2745,6 +2815,9 @@ See + + + @@ -2753,7 +2826,7 @@ See - + @@ -2956,6 +3029,7 @@ See + @@ -2966,6 +3040,8 @@ See + + @@ -2979,7 +3055,7 @@ See - + diff --git a/MasterPassword/Resources/Media/meter_fuel.png b/MasterPassword/Resources/Media/meter_fuel.png new file mode 100644 index 00000000..5150bbac Binary files /dev/null and b/MasterPassword/Resources/Media/meter_fuel.png differ diff --git a/MasterPassword/Resources/Media/meter_fuel@2x.png b/MasterPassword/Resources/Media/meter_fuel@2x.png new file mode 100644 index 00000000..1fe34216 Binary files /dev/null and b/MasterPassword/Resources/Media/meter_fuel@2x.png differ diff --git a/MasterPassword/Resources/Media/meter_fuel@3x.png b/MasterPassword/Resources/Media/meter_fuel@3x.png new file mode 100644 index 00000000..36dd4323 Binary files /dev/null and b/MasterPassword/Resources/Media/meter_fuel@3x.png differ diff --git a/MasterPassword/Resources/Media/thumb_fuel.png b/MasterPassword/Resources/Media/thumb_fuel.png new file mode 100644 index 00000000..06e530c6 Binary files /dev/null and b/MasterPassword/Resources/Media/thumb_fuel.png differ diff --git a/MasterPassword/Resources/Media/thumb_fuel@2x.png b/MasterPassword/Resources/Media/thumb_fuel@2x.png new file mode 100644 index 00000000..8f218bb2 Binary files /dev/null and b/MasterPassword/Resources/Media/thumb_fuel@2x.png differ diff --git a/MasterPassword/Resources/Media/thumb_fuel@3x.png b/MasterPassword/Resources/Media/thumb_fuel@3x.png new file mode 100644 index 00000000..2c70afbd Binary files /dev/null and b/MasterPassword/Resources/Media/thumb_fuel@3x.png differ diff --git a/MasterPassword/Resources/Raw/Store_Thumb.psd b/MasterPassword/Resources/Raw/Store_Thumb.psd index 7ca264b4..77f2904d 100644 Binary files a/MasterPassword/Resources/Raw/Store_Thumb.psd and b/MasterPassword/Resources/Raw/Store_Thumb.psd differ