2
0

Development fuel, store improvements and navigation fixes.

This commit is contained in:
Maarten Billemont 2014-09-26 00:32:07 -04:00
parent a5bc2eb584
commit 6050b5d6fd
27 changed files with 463 additions and 156 deletions

View File

@ -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<MPAlgorithm> algorithm = nil;
if (!name.length)

View File

@ -6,21 +6,32 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <StoreKit/StoreKit.h>
#import "MPAppDelegate_Shared.h"
#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<MPInAppDelegate>)delegate;
- (void)removeProductsObserver:(id<MPInAppDelegate>)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

View File

@ -7,7 +7,6 @@
//
#import "MPAppDelegate_InApp.h"
#import <StoreKit/StoreKit.h>
@interface MPAppDelegate_Shared(InApp_Private)<SKProductsRequestDelegate, SKPaymentTransactionObserver>
@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<MPInAppDelegate>)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<MPInAppDelegate>)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<MPInAppDelegate> 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<MPInAppDelegate> productObserver in self.productObservers)
[productObserver updateWithTransaction:transaction];
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {

View File

@ -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];

View File

@ -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

View File

@ -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;

View File

@ -29,11 +29,10 @@
_dismissSegueByButton = [NSMutableDictionary dictionary];
}
- (void)viewWillAppear:(BOOL)animated {
- (void)viewDidLoad {
[super viewWillAppear:animated];
[super viewDidLoad];
if (![self.childViewControllers count])
[self performSegueWithIdentifier:@"root" sender:self];
}
@ -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];
[containerViewController setNeedsStatusBarAppearanceUpdate];
}];
}
else {

View File

@ -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];

View File

@ -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)

View File

@ -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) {

View File

@ -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 <lhunath@lyndir.com>
* @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 <Foundation/Foundation.h>
@interface MPRootSegue : UIStoryboardSegue
@end

View File

@ -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 <lhunath@lyndir.com>
* @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

View File

@ -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

View File

@ -10,11 +10,14 @@
#import "MPiOSAppDelegate.h"
#import "UIColor+Expanded.h"
#import "MPAppDelegate_InApp.h"
#import <StoreKit/StoreKit.h>
@interface MPStoreViewController()
PearlEnum( MPDevelopmentFuelConsumption,
MPDevelopmentFuelConsumptionQuarterly, MPDevelopmentFuelConsumptionMonthly, MPDevelopmentFuelWeekly );
@interface MPStoreViewController()<MPInAppDelegate>
@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,66 +133,20 @@
} cancelTitle:@"Cancel" otherTitles:@"Find Purchases", nil];
}
#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;
if ([productIdentifier isEqualToString:MPProductGenerateAnswers])
return self.generateAnswersCell;
return nil;
}
#pragma mark - MPInAppDelegate
- (void)updateWithProducts:(NSArray *)products {
NSMutableArray *showCells = [NSMutableArray array];
NSMutableArray *hideCells = [NSMutableArray array];
[hideCells addObjectsFromArray:self.allCellsBySection[0]];
self.products = products;
for (SKProduct *product in products) {
[self showCellForProductWithIdentifier:MPProductGenerateLogins ifProduct:product showingCells:showCells];
[self showCellForProductWithIdentifier:MPProductGenerateAnswers ifProduct:product showingCells:showCells];
[self updateProducts];
}
[hideCells removeObjectsInArray:showCells];
if ([self.tableView numberOfRowsInSection:0])
[self updateCellsHiding:hideCells showing:showCells animation:UITableViewRowAnimationAutomatic];
else
[self updateCellsHiding:hideCells showing:showCells animation:UITableViewRowAnimationNone];
}
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction {
- (void)showCellForProductWithIdentifier:(NSString *)productIdentifier ifProduct:(SKProduct *)product
showingCells:(NSMutableArray *)showCells {
if (![product.productIdentifier isEqualToString:productIdentifier])
return;
MPStoreProductCell *cell = [self cellForProductIdentifier:productIdentifier];
[showCells addObject:cell];
self.currencyFormatter.locale = product.priceLocale;
BOOL purchased = [[MPiOSAppDelegate get] isPurchased:productIdentifier];
cell.priceLabel.text = purchased? @"": [self.currencyFormatter stringFromNumber:product.price];
cell.purchasedIndicator.alpha = purchased? 1: 0;
}
- (void)updateWithTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
MPStoreProductCell *cell = [self cellForProductIdentifier:transaction.payment.productIdentifier];
if (!cell)
continue;
return;
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
@ -212,6 +166,103 @@
break;
}
}
#pragma mark - Private
- (SKProduct *)productForCell:(MPStoreProductCell *)cell {
for (SKProduct *product in self.products)
if ([self cellForProductIdentifier:product.productIdentifier] == cell)
return product;
return nil;
}
- (MPStoreProductCell *)cellForProductIdentifier:(NSString *)productIdentifier {
if ([productIdentifier isEqualToString:MPProductGenerateLogins])
return self.generateLoginCell;
if ([productIdentifier isEqualToString:MPProductGenerateAnswers])
return self.generateAnswersCell;
if ([productIdentifier isEqualToString:MPProductFuel])
return self.fuelCell;
return nil;
}
- (void)updateProducts {
NSMutableArray *showCells = [NSMutableArray array];
NSMutableArray *hideCells = [NSMutableArray array];
[hideCells addObjectsFromArray:self.allCellsBySection[0]];
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];
if ([self.tableView numberOfRowsInSection:0])
[self updateCellsHiding:hideCells showing:showCells animation:UITableViewRowAnimationAutomatic];
else
[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 {
if (![product.productIdentifier isEqualToString:productIdentifier])
return;
MPStoreProductCell *cell = [self cellForProductIdentifier:productIdentifier];
[showCells addObject:cell];
self.currencyFormatter.locale = product.priceLocale;
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;
}
- (NSInteger)quantityForProductIdentifier:(NSString *)productIdentifier {
if ([productIdentifier isEqualToString:MPProductFuel])
return (NSInteger)(MP_FUEL_HOURLY_RATE * [self weeklyFuelConsumption]);
return 1;
}
@end

View File

@ -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;
}];
}],

View File

@ -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 {

View File

@ -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

View File

@ -9,6 +9,7 @@
@implementation MPiOSConfig
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch;
@dynamic developmentFuel, developmentFuelConsumption, developmentFuelChecked;
- (id)init {
@ -24,7 +25,7 @@
NSStringFromSelector( @selector( typeTipShown ) ) : @(!self.firstRun),
NSStringFromSelector( @selector( loginNameTipShown ) ) : @NO,
NSStringFromSelector( @selector( traceMode ) ) : @NO,
NSStringFromSelector( @selector(dictationSearch) ) : @NO
NSStringFromSelector( @selector( dictationSearch ) ) : @NO,
}];
return self;

View File

@ -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 = "<group>"; };
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadFromArray.h"; sourceTree = "<group>"; };
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = "<group>"; };
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; };
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; };
93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = "<group>"; };
@ -446,6 +456,7 @@
93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlNavigationController.h; sourceTree = "<group>"; };
93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebViewController.m; sourceTree = "<group>"; };
93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRootSegue.m; sourceTree = "<group>"; };
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = "<group>"; };
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = "<group>"; };
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = "<group>"; };
@ -552,6 +563,12 @@
DA32D03F19D27093004F3F0E /* thumb_generated_answers@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_generated_answers@3x.png"; sourceTree = "<group>"; };
DA32D04019D27093004F3F0E /* thumb_generated_answers@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_generated_answers@2x.png"; sourceTree = "<group>"; };
DA32D04119D27093004F3F0E /* thumb_generated_answers.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumb_generated_answers.png; sourceTree = "<group>"; };
DA32D04519D2F417004F3F0E /* thumb_fuel@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_fuel@3x.png"; sourceTree = "<group>"; };
DA32D04619D2F417004F3F0E /* thumb_fuel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumb_fuel@2x.png"; sourceTree = "<group>"; };
DA32D04719D2F417004F3F0E /* thumb_fuel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumb_fuel.png; sourceTree = "<group>"; };
DA32D04B19D2F59B004F3F0E /* meter_fuel@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "meter_fuel@3x.png"; sourceTree = "<group>"; };
DA32D04C19D2F59B004F3F0E /* meter_fuel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "meter_fuel@2x.png"; sourceTree = "<group>"; };
DA32D04D19D2F59B004F3F0E /* meter_fuel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = meter_fuel.png; sourceTree = "<group>"; };
DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = "<group>"; };
DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = "<group>"; };
DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Storyboard.storyboard; sourceTree = "<group>"; };
@ -1525,6 +1542,8 @@
93D3957D76F71A652716EECC /* MPStoreViewController.m */,
93D39C426E03358384018E85 /* MPAnswersViewController.m */,
93D39D6604447D7708039155 /* MPAnswersViewController.h */,
93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */,
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */,
);
sourceTree = "<group>";
};
@ -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;

View File

@ -28,6 +28,7 @@
<string>Exo2.0-Bold</string>
<string>Exo2.0-Bold</string>
<string>Exo2.0-Bold</string>
<string>Exo2.0-Bold</string>
</mutableArray>
<mutableArray key="Exo2.0-ExtraBold.otf">
<string>Exo2.0-ExtraBold</string>
@ -65,6 +66,8 @@
<string>Exo2.0-Regular</string>
<string>Exo2.0-Regular</string>
<string>Exo2.0-Regular</string>
<string>Exo2.0-Regular</string>
<string>Exo2.0-Regular</string>
</mutableArray>
<mutableArray key="Exo2.0-Thin.otf">
<string>Exo2.0-Thin</string>
@ -84,6 +87,7 @@
<string>Exo2.0-Thin</string>
<string>Exo2.0-Thin</string>
<string>Exo2.0-Thin</string>
<string>Exo2.0-Thin</string>
</mutableArray>
<mutableArray key="SourceCodePro-Black.otf">
<string>SourceCodePro-Black</string>
@ -112,11 +116,11 @@
<viewControllerLayoutGuide type="bottom" id="VGz-R0-vMD"/>
</layoutGuides>
<view key="view" clipsSubviews="YES" contentMode="scaleToFill" id="DOr-Xu-P9q" userLabel="Root">
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="X8H-vh-j7B" userLabel="Keyboard">
<rect key="frame" x="0.0" y="431" width="375" height="216"/>
<rect key="frame" x="0.0" y="451" width="375" height="216"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="216" id="GcG-kq-fq2"/>
@ -124,13 +128,13 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rWM-08-aab" userLabel="Users Root">
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="VDd-oM-ZOO" userLabel="Store Activity">
<rect key="frame" x="169" y="305" width="37" height="37"/>
<rect key="frame" x="169" y="315" width="37" height="37"/>
</activityIndicatorView>
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" alpha="0.0" contentMode="scaleToFill" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="L6J-pd-gcp" userLabel="Avatar Collection">
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="ATB-kM-EGu">
<size key="itemSize" width="215" height="647"/>
@ -147,7 +151,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<navigationBar hidden="YES" contentMode="scaleToFill" verticalHuggingPriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="CQo-kd-XAU">
<rect key="frame" x="0.0" y="0.0" width="215" height="44"/>
<rect key="frame" x="0.0" y="20" width="215" height="44"/>
</navigationBar>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="J7b-uT-zY2" userLabel="Keyboard">
<rect key="frame" x="0.0" y="431" width="215" height="216"/>
@ -157,10 +161,10 @@
</constraints>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_spinner.png" translatesAutoresizingMaskIntoConstraints="NO" id="y4j-ds-HM7" userLabel="Spinner">
<rect key="frame" x="52" y="-33" width="110" height="110"/>
<rect key="frame" x="52" y="-13" width="110" height="110"/>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar-0.png" translatesAutoresizingMaskIntoConstraints="NO" id="Aca-he-7Qi" userLabel="Avatar">
<rect key="frame" x="52" y="-33" width="110" height="110"/>
<rect key="frame" x="52" y="-13" width="110" height="110"/>
<color key="tintColor" red="0.47450980390000003" green="0.86666666670000003" blue="0.98431372549999996" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="110" id="Ezz-dq-dfq"/>
@ -168,7 +172,7 @@
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0Sa-Vg-EEI" userLabel="Name Backdrop">
<rect key="frame" x="43" y="14" width="128.5" height="16"/>
<rect key="frame" x="43" y="34" width="128.5" height="16"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalCompressionResistancePriority="1000" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cLT-s0-4SQ" userLabel="Name Field">
<rect key="frame" x="5" y="0.0" width="118.5" height="16"/>
@ -201,7 +205,7 @@
<constraint firstAttribute="trailing" secondItem="J7b-uT-zY2" secondAttribute="trailing" id="SFQ-jz-Kj4"/>
<constraint firstItem="y4j-ds-HM7" firstAttribute="centerY" secondItem="Aca-he-7Qi" secondAttribute="centerY" id="b7q-13-zb4"/>
<constraint firstAttribute="centerY" secondItem="Aca-he-7Qi" secondAttribute="centerY" priority="500" id="fKx-ZZ-sJa"/>
<constraint firstItem="CQo-kd-XAU" firstAttribute="top" secondItem="Zab-uQ-uk9" secondAttribute="top" id="kO6-Hn-9ab"/>
<constraint firstItem="CQo-kd-XAU" firstAttribute="top" secondItem="Zab-uQ-uk9" secondAttribute="top" constant="20" id="kO6-Hn-9ab"/>
<constraint firstAttribute="bottom" secondItem="J7b-uT-zY2" secondAttribute="bottom" id="sKD-RY-oA8"/>
<constraint firstItem="CQo-kd-XAU" firstAttribute="leading" secondItem="Zab-uQ-uk9" secondAttribute="leading" id="trm-Bp-zf3"/>
<constraint firstItem="y4j-ds-HM7" firstAttribute="height" secondItem="Aca-he-7Qi" secondAttribute="height" id="wyT-4c-SaV"/>
@ -233,7 +237,7 @@
</connections>
</collectionView>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
<rect key="frame" x="0.0" y="224.5" width="44" height="53"/>
<rect key="frame" x="0.0" y="244" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="Ay6-Jg-c3T"/>
</constraints>
@ -246,7 +250,7 @@
</connections>
</button>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
<rect key="frame" x="331" y="224.5" width="44" height="53"/>
<rect key="frame" x="331" y="244" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="oAm-YX-Fx5"/>
</constraints>
@ -259,7 +263,7 @@
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry">
<rect key="frame" x="20" y="340.5" width="335" height="70.5"/>
<rect key="frame" x="20" y="360" width="335" height="70.5"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your full name:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="5fe-rt-zFa" userLabel="Entry Label">
<rect key="frame" x="20" y="0.0" width="295" height="20.5"/>
@ -337,7 +341,7 @@
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XEP-O3-ayG" userLabel="Footer">
<rect key="frame" x="0.0" y="575" width="375" height="72"/>
<rect key="frame" x="0.0" y="595" width="375" height="72"/>
<subviews>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4md-Gp-SLG">
<rect key="frame" x="20" y="48" width="335" height="24"/>
@ -410,12 +414,12 @@
<constraints>
<constraint firstItem="VGz-R0-vMD" firstAttribute="top" secondItem="X8H-vh-j7B" secondAttribute="bottom" id="8Ed-3Y-ll0"/>
<constraint firstAttribute="trailing" secondItem="X8H-vh-j7B" secondAttribute="trailing" id="8Gr-Dq-UpZ"/>
<constraint firstAttribute="bottom" secondItem="rWM-08-aab" secondAttribute="bottom" id="9Yx-cj-wHh"/>
<constraint firstItem="X8H-vh-j7B" firstAttribute="top" secondItem="9u7-pu-Wtv" secondAttribute="centerY" constant="180" id="Gp5-h6-53S"/>
<constraint firstItem="rWM-08-aab" firstAttribute="leading" secondItem="DOr-Xu-P9q" secondAttribute="leading" id="Il8-kg-Dra"/>
<constraint firstItem="X8H-vh-j7B" firstAttribute="top" secondItem="fUK-gJ-NRE" secondAttribute="centerY" constant="180" id="PgC-ZL-cQo"/>
<constraint firstAttribute="trailing" secondItem="rWM-08-aab" secondAttribute="trailing" id="UPP-1n-zIe"/>
<constraint firstItem="X8H-vh-j7B" firstAttribute="top" secondItem="qp1-nX-o4i" secondAttribute="bottom" constant="20" id="WdK-tA-njz"/>
<constraint firstItem="VGz-R0-vMD" firstAttribute="top" secondItem="rWM-08-aab" secondAttribute="bottom" id="fcH-lm-76a"/>
<constraint firstItem="X8H-vh-j7B" firstAttribute="leading" secondItem="DOr-Xu-P9q" secondAttribute="leading" id="jbn-ko-MPq"/>
<constraint firstAttribute="top" secondItem="rWM-08-aab" secondAttribute="top" id="xBe-1Q-mz2"/>
</constraints>
@ -537,7 +541,7 @@
</view>
<navigationItem key="navigationItem" id="V6W-ql-3TD"/>
<connections>
<segue destination="Ac5-na-hOV" kind="custom" identifier="root" customClass="MPOverlaySegue" id="UKS-gd-oD2"/>
<segue destination="Ac5-na-hOV" kind="custom" identifier="root" customClass="MPRootSegue" id="UKS-gd-oD2"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="fQY-fV-sIe" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -559,30 +563,20 @@
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="Lkg-xn-bce" userLabel="Background">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
</imageView>
<containerView clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jDK-5y-QRP" userLabel="Users">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<connections>
<segue destination="S8q-YF-Kt9" kind="embed" identifier="users" id="9AR-TX-BkY"/>
</connections>
</containerView>
</subviews>
<constraints>
<constraint firstItem="lXD-AZ-Umi" firstAttribute="top" secondItem="Lkg-xn-bce" secondAttribute="bottom" id="4xV-YK-k7B"/>
<constraint firstItem="Lkg-xn-bce" firstAttribute="top" secondItem="fkJ-D0-yue" secondAttribute="top" id="EIy-Cd-0vW"/>
<constraint firstItem="jDK-5y-QRP" firstAttribute="top" secondItem="zOG-3W-l4v" secondAttribute="bottom" id="GFh-An-RJs"/>
<constraint firstAttribute="trailing" secondItem="jDK-5y-QRP" secondAttribute="trailing" id="L2M-Eb-kwd"/>
<constraint firstAttribute="trailing" secondItem="Lkg-xn-bce" secondAttribute="trailing" id="ROW-fK-z92"/>
<constraint firstItem="lXD-AZ-Umi" firstAttribute="top" secondItem="jDK-5y-QRP" secondAttribute="bottom" id="TUm-Qo-P4W"/>
<constraint firstItem="Lkg-xn-bce" firstAttribute="leading" secondItem="fkJ-D0-yue" secondAttribute="leading" id="UH5-Kk-taJ"/>
<constraint firstItem="jDK-5y-QRP" firstAttribute="leading" secondItem="fkJ-D0-yue" secondAttribute="leading" id="jPw-0w-Fbo"/>
<constraint firstAttribute="bottom" secondItem="Lkg-xn-bce" secondAttribute="bottom" id="txR-pf-v3l"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="MPa-zX-Kaq"/>
<nil key="simulatedTopBarMetrics"/>
<connections>
<outlet property="usersView" destination="jDK-5y-QRP" id="cuP-gN-eBE"/>
<segue destination="osn-5H-SWW" kind="custom" identifier="emergency" customClass="MPOverlaySegue" id="gtX-Cx-AA2"/>
<segue destination="nkY-z6-8jd" kind="custom" identifier="passwords" customClass="MPPasswordsSegue" id="Ozp-YT-Utx"/>
<segue destination="S8q-YF-Kt9" kind="custom" identifier="users" customClass="MPRootSegue" id="StK-nr-nps"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="F33-Fe-Tb6" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -2692,6 +2686,82 @@ See </string>
<outlet property="purchasedIndicator" destination="yZX-ns-8oV" id="7x0-eq-oSs"/>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" shouldIndentWhileEditing="NO" rowHeight="328" id="le3-Q5-MSO" userLabel="Fuel" customClass="MPStoreProductCell">
<rect key="frame" x="0.0" y="0.0" width="320" height="97"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="le3-Q5-MSO" id="SzQ-Y5-XIF">
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Fuel Top-Up" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" preferredMaxLayoutWidth="292" translatesAutoresizingMaskIntoConstraints="NO" id="Jnv-uN-xeg">
<rect key="frame" x="20" y="226" width="292" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fz2-AO-aGW">
<rect key="frame" x="20" y="254" width="335" height="53.5"/>
<string key="text">You really love Master Password and how it's solving your password problems. You're eager to encourage the maintenance, technical support and development of new features. I am a one-man shop, more fuel means I can allocate more hours to Master Password.</string>
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="11"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="thumb_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="PnG-hP-syh">
<rect key="frame" x="88" y="20" width="198" height="198"/>
</imageView>
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="eS4-59-Xny">
<rect key="frame" x="169" y="100" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="$2.95" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="35.5" translatesAutoresizingMaskIntoConstraints="NO" id="EbU-DV-fKF">
<rect key="frame" x="320" y="226" width="34.5" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="meter_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="aGb-QC-A92">
<rect key="frame" x="261" y="208" width="12" height="10"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dsR-fr-dY4">
<rect key="frame" x="20" y="20" width="107" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="W6p-kB-VBX"/>
</constraints>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<state key="normal" title="1h / quarter" image="icon_meter.png">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="toggleFuelConsumption:" destination="pdl-xv-zjX" eventType="touchUpInside" id="NkB-Dy-IeY"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="aGb-QC-A92" firstAttribute="bottom" secondItem="PnG-hP-syh" secondAttribute="bottom" id="0Jx-M5-xlh"/>
<constraint firstItem="Jnv-uN-xeg" firstAttribute="leading" secondItem="SzQ-Y5-XIF" secondAttribute="leading" constant="20" symbolic="YES" id="1ng-ce-JH9"/>
<constraint firstItem="Jnv-uN-xeg" firstAttribute="top" secondItem="EbU-DV-fKF" secondAttribute="top" id="F04-ip-fHo"/>
<constraint firstItem="fz2-AO-aGW" firstAttribute="top" secondItem="Jnv-uN-xeg" secondAttribute="bottom" constant="8" symbolic="YES" id="F3H-iv-edF"/>
<constraint firstItem="fz2-AO-aGW" firstAttribute="leading" secondItem="SzQ-Y5-XIF" secondAttribute="leading" constant="20" symbolic="YES" id="G6B-g9-Z35"/>
<constraint firstItem="PnG-hP-syh" firstAttribute="top" secondItem="SzQ-Y5-XIF" secondAttribute="top" constant="20" symbolic="YES" id="JNg-lM-Jns"/>
<constraint firstItem="Jnv-uN-xeg" firstAttribute="bottom" secondItem="EbU-DV-fKF" secondAttribute="bottom" id="KbL-rF-pVN"/>
<constraint firstItem="Jnv-uN-xeg" firstAttribute="top" secondItem="PnG-hP-syh" secondAttribute="bottom" constant="8" symbolic="YES" id="OZV-m1-YZ1"/>
<constraint firstItem="dsR-fr-dY4" firstAttribute="top" secondItem="SzQ-Y5-XIF" secondAttribute="top" constant="20" id="VH2-O8-CGj"/>
<constraint firstAttribute="bottom" secondItem="fz2-AO-aGW" secondAttribute="bottom" constant="20" symbolic="YES" id="Wqo-Le-AcG"/>
<constraint firstItem="eS4-59-Xny" firstAttribute="centerX" secondItem="PnG-hP-syh" secondAttribute="centerX" id="ZbQ-LX-kmS"/>
<constraint firstItem="EbU-DV-fKF" firstAttribute="leading" secondItem="Jnv-uN-xeg" secondAttribute="trailing" constant="8" symbolic="YES" id="cku-JX-4bK"/>
<constraint firstItem="aGb-QC-A92" firstAttribute="centerX" secondItem="PnG-hP-syh" secondAttribute="centerX" constant="80" id="eMa-Gj-BUc"/>
<constraint firstItem="dsR-fr-dY4" firstAttribute="leading" secondItem="SzQ-Y5-XIF" secondAttribute="leading" constant="20" id="eX0-7y-eHi"/>
<constraint firstAttribute="centerX" secondItem="PnG-hP-syh" secondAttribute="centerX" id="gO5-ME-YVO"/>
<constraint firstAttribute="trailing" secondItem="EbU-DV-fKF" secondAttribute="trailing" constant="20" id="hae-Jv-wOU"/>
<constraint firstAttribute="trailing" secondItem="fz2-AO-aGW" secondAttribute="trailing" constant="20" id="vlt-qH-1Xx"/>
<constraint firstItem="eS4-59-Xny" firstAttribute="centerY" secondItem="PnG-hP-syh" secondAttribute="centerY" id="yUc-2F-y1r"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<connections>
<outlet property="activityIndicator" destination="eS4-59-Xny" id="kGW-fn-VqH"/>
<outlet property="priceLabel" destination="EbU-DV-fKF" id="pg2-8o-7We"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="hN1-J4-w3w">
@ -2745,6 +2815,9 @@ See </string>
<nil key="simulatedStatusBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="fuelCell" destination="le3-Q5-MSO" id="oAk-6g-cFj"/>
<outlet property="fuelMeterConstraint" destination="eMa-Gj-BUc" id="9iF-EO-UU6"/>
<outlet property="fuelSpeedButton" destination="dsR-fr-dY4" id="XGI-PE-9mh"/>
<outlet property="generateAnswersCell" destination="l1g-Ul-Vg8" id="GlG-iZ-7FP"/>
<outlet property="generateLoginCell" destination="JVW-tG-xxe" id="PXM-WX-8Qe"/>
<outlet property="iOSIntegrationCell" destination="9Na-CL-jBq" id="LSO-OV-9KA"/>
@ -2753,7 +2826,7 @@ See </string>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="WvF-bk-cgx" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2624.5" y="463.5"/>
<point key="canvasLocation" x="2624.5" y="427.5"/>
</scene>
<!--Answers View Controller-->
<scene sceneID="OG6-nJ-1A0">
@ -2956,6 +3029,7 @@ See </string>
<image name="icon_edit.png" width="32" height="32"/>
<image name="icon_gears.png" width="32" height="32"/>
<image name="icon_list-names.png" width="32" height="32"/>
<image name="icon_meter.png" width="32" height="32"/>
<image name="icon_person.png" width="32" height="32"/>
<image name="icon_plus.png" width="32" height="32"/>
<image name="icon_question.png" width="32" height="32"/>
@ -2966,6 +3040,8 @@ See </string>
<image name="icon_up.png" width="32" height="32"/>
<image name="identity.png" width="82" height="80"/>
<image name="image-0.png" width="320" height="568"/>
<image name="meter_fuel.png" width="12" height="10"/>
<image name="thumb_fuel.png" width="198" height="198"/>
<image name="thumb_generated_answers.png" width="198" height="198"/>
<image name="thumb_generated_login.png" width="198" height="198"/>
<image name="tip_basic_black.png" width="210" height="60"/>
@ -2979,7 +3055,7 @@ See </string>
<simulatedScreenMetrics key="destination" type="retina47"/>
</simulatedMetricsContainer>
<inferredMetricsTieBreakers>
<segue reference="GZk-I4-JyH"/>
<segue reference="gtb-zE-u9H"/>
<segue reference="Ql4-wf-T8u"/>
</inferredMetricsTieBreakers>
<color key="tintColor" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB