From 6050b5d6fd26e13da22d3ae04509915b5dcc44c4 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Fri, 26 Sep 2014 00:32:07 -0400 Subject: [PATCH] Development fuel, store improvements and navigation fixes. --- MasterPassword/ObjC/MPAlgorithmV0.m | 2 +- MasterPassword/ObjC/MPAppDelegate_InApp.h | 25 ++- MasterPassword/ObjC/MPAppDelegate_InApp.m | 86 +++++++++-- MasterPassword/ObjC/MPAppDelegate_Store.m | 2 +- .../ObjC/iOS/MPCombinedViewController.h | 10 +- .../ObjC/iOS/MPCombinedViewController.m | 12 +- .../ObjC/iOS/MPOverlayViewController.m | 16 +- MasterPassword/ObjC/iOS/MPPasswordCell.m | 4 +- MasterPassword/ObjC/iOS/MPPasswordsSegue.m | 8 +- .../ObjC/iOS/MPPasswordsViewController.m | 28 ++-- MasterPassword/ObjC/iOS/MPRootSegue.h | 25 +++ MasterPassword/ObjC/iOS/MPRootSegue.m | 38 +++++ .../ObjC/iOS/MPStoreViewController.h | 3 + .../ObjC/iOS/MPStoreViewController.m | 145 ++++++++++++------ .../ObjC/iOS/MPUsersViewController.m | 14 +- MasterPassword/ObjC/iOS/MPiOSAppDelegate.m | 8 +- MasterPassword/ObjC/iOS/MPiOSConfig.h | 3 + MasterPassword/ObjC/iOS/MPiOSConfig.m | 19 +-- .../project.pbxproj | 35 +++++ MasterPassword/ObjC/iOS/Storyboard.storyboard | 136 ++++++++++++---- MasterPassword/Resources/Media/meter_fuel.png | Bin 0 -> 366 bytes .../Resources/Media/meter_fuel@2x.png | Bin 0 -> 437 bytes .../Resources/Media/meter_fuel@3x.png | Bin 0 -> 1537 bytes MasterPassword/Resources/Media/thumb_fuel.png | Bin 0 -> 906 bytes .../Resources/Media/thumb_fuel@2x.png | Bin 0 -> 1270 bytes .../Resources/Media/thumb_fuel@3x.png | Bin 0 -> 5319 bytes MasterPassword/Resources/Raw/Store_Thumb.psd | Bin 7274342 -> 7320100 bytes 27 files changed, 463 insertions(+), 156 deletions(-) create mode 100644 MasterPassword/ObjC/iOS/MPRootSegue.h create mode 100644 MasterPassword/ObjC/iOS/MPRootSegue.m create mode 100644 MasterPassword/Resources/Media/meter_fuel.png create mode 100644 MasterPassword/Resources/Media/meter_fuel@2x.png create mode 100644 MasterPassword/Resources/Media/meter_fuel@3x.png create mode 100644 MasterPassword/Resources/Media/thumb_fuel.png create mode 100644 MasterPassword/Resources/Media/thumb_fuel@2x.png create mode 100644 MasterPassword/Resources/Media/thumb_fuel@3x.png 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 0000000000000000000000000000000000000000..5150bbac97959fb0fec96f05b61fc735bc537755 GIT binary patch literal 366 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CF!2%?;RLcv26id3JuOkD)#(wTUiL5}rLb6AY zF9SoB8UsT^3j@P1pisjL28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeMy z@pN$v;kcfAa-*PwfrP`u`A*OM)?S|BkjU=5!`LOQL(wE{L-QdPwt$S%t_m)3rJASJ zO6De0YXXZ5gnwG5Fgg5O{BYC6U6zSQU)^0;Az?H-SuAF*tZsgrlgo*lAA4C29hm=% zMd4Q46s?sx+(7$OOI#yLQW8s2t&)pUffR$0fuV`6fu*jINr-`km5HU5p}DSsiIsuD zzI-2D6b-rgDVb@NxHX(N`Z5uyK@wy`aDG}zd16s2gJVj5QmTSyZen_BP-NZi=%7QY`6?zK#qG8~eHcB(ehe3dtTp zz6=aiY77hwEes65fI>gTe~DWM4f4ZWEs literal 0 HcmV?d00001 diff --git a/MasterPassword/Resources/Media/meter_fuel@3x.png b/MasterPassword/Resources/Media/meter_fuel@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..36dd4323652ed3124bf95823346dee9621b854d8 GIT binary patch literal 1537 zcmbVMeM}Q)7=KG!C|IxpVPK}7habgxz1~M#?nX!9j!HL+tP;g-F5LC5aD(>B^^oHJ zsel6$bjUViWK-EPaa-a>W^{8OY9LdmS>jwW=1i8DxonFGvPoo%$gW7;{xJTrOD^|5 z?>)cY^Sr<3dEQ;MHS4mo7H0tfvVGNTK>HSJ?-Elw0J_b`Zscl4ky=%+Zj?i6JEw@C zLXde8@`brZF(7h6N9zT#3;=yxs;gJ){p;zV9JX;uA6q;e)zAQxmB*u8uvt_gUTl;i zZg}Fe(=a3nZg{i9Z}&$Tu}P}lu814A*VF~KHwP&JE`JIti_;oGSX4PE9&U-m=(ro6 ze5fTqN5gsEs#Az2wT_N6u5hRx4c^IBz8K%mPI|+*NVptVf>SVA= z#z_*SgTcL3l_{(*5>vTIP)x;^wAk}l$0M;cqlla;D|NEmlInumCRvqZO>z`sn7OJ$ zRzDY%BFW8?WTEb#n-vvln<#h{ISkDyO-m2SLUd9jC5R;A#0gCSKExrEgVPicNN32& zk%YrZ!1H+F|0;-TNukMh`A_?#wzQs1E*}hoW;_@tF`~_oq76&#g6}H;NL%J(E9&A` z`RUo&a(({GqvL&0T9(J`wRC0rK+UM(S>OT7n;SiwrWPN`K9Y8%Z*S%<{XSrV%tZfN zoe`V9VAu$;%fHN>d3DL>89y3UoVdGMqcsf<+QF&8eQ*C|ZZ@4Q?eyH$4Z><(Ve6J2 z^QNJ}?u%2mrgxtUE-t%n+Nmq{01SG!f268z0NKlE`6OR~lPh>E?a? z=QH!3!N->D|HOj~CAQu>xz^HE1=cqBt4Uxu_|Ca+ zBpinb%eu4t@JzXwI6839GM;5==*vHFcF*geVr1&I+)-<5A8Gjs@Jp?;d6p0KlM9q= z0p55Bv6Ooilj)Xjl7LP?Pd4>hM=rOnTC~f$(;V|{yTs-Orr77`(6XW*TF{a`%U9`Q zy2&zg%M^}7d92+p-Hg4EaukSs5CikN?meWO` h=gjXXU9~Pd02gns`OWd+^DibZWnWbdd#tjd^B>oN1~LEu literal 0 HcmV?d00001 diff --git a/MasterPassword/Resources/Media/thumb_fuel.png b/MasterPassword/Resources/Media/thumb_fuel.png new file mode 100644 index 0000000000000000000000000000000000000000..06e530c6f4492c237c560c2b41e7a32aea9e3537 GIT binary patch literal 906 zcmeAS@N?(olHy`uVBq!ia0vp^$3U2a1xPASGQS3-SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XkBG zr;B4q#jUq@Pv;5;GB{jhe2g*FEOcJU<`3!FL7YaWZ>-dS4iue zBB{cD{LI$cb462n?!WkDTdh-_#sf4M10>wryOh14#%%S*c{zLbd@p=iICsg}pJ}iD zn*Fb=f4T5;yM$s=ONXG6TL&48PYEv>WwxDvU07Ird;8U!_V0Xq?yfoZ^Q`ae`g=1| zwOo1x44sZ}2r3i9II`V&TbX6C;)&H?4_nu;7;f7xH~F;PKbdmL!$zXyIq#8lYyHQ} zBHs1uAD^5jfB6hnmmE2+nB3FPE3#B z&lkykNS=GL;=b?R=>Dshl6Ukj?laUom7lU)Cm0&4Cob-3*k5a3yK&#H%i2@l&z}hl z=@X86!TD)^&(?*v|KDSELZVo`cz<8QB>8`WG86xtsi?L4tM;bb-$GT@me#oue_GRPqH;TtT zpEUk=`qj%XWzEh#v&(uu=4?J2AFE$Gh5e9c{E2m^ubf`W|G*(e&lZwc@?M)qsXFEF zm@=mgD4<&68c~vxSdwa$T$Bo=7>o=IO>_+`b&X6y3@ofnjIE4~bq!3c3=HJe|97Ei z$jwj5OsmALA+O5a45&d8WCJh&O3D+9QW+dm@{>{(JaZG%Q-e|yQz{Ejrh+mEgQu&X J%Q~loCIB~yRy_a! literal 0 HcmV?d00001 diff --git a/MasterPassword/Resources/Media/thumb_fuel@2x.png b/MasterPassword/Resources/Media/thumb_fuel@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8f218bb291dbfa618a7b7d4e77cd35f16ff0eed7 GIT binary patch literal 1270 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 literal 0 HcmV?d00001 diff --git a/MasterPassword/Resources/Raw/Store_Thumb.psd b/MasterPassword/Resources/Raw/Store_Thumb.psd index 7ca264b491f5c2656127d6ac2dad29a861ffc5d8..77f2904d87188ae9ffb4df40c1d23ddf9949b872 100644 GIT binary patch delta 26734 zcmeI5eOy#k8pqGv89>PwL_|f9Nnk|<1csN!wnv1x3n4MD zBgbwp-}S+v{%&P8d;jeA?yP;&+~l6Ib0gK0Rne0p)m)lFYs5h7&CK5mZ&RlpDYel9uTMVUMILqg~< zc`@vJ6jm6j%Ti|O4EjRYWw^2I7)5oZwt`Dz7e+HG@S&O7f+`xP6G^Hvl=A!OI6kLv zi8UT%{*qlR!`(CTCeDX7%T(qMSv~K05qndw680$GOluUX6J}&oqZgi?IuC{wsc;f>nwPW)}Ol6`ZJSD%|Gu$>osQ-PklRcB)Yl#$J@#0GEQDg?g-ui zvr08tj`w`K>8^>l4m==tyR4`P9=GSe3D29ZH=j>QIh|M@ls>B|VC#%JX3sY-e$(*i zLP3}R=-K66J+5EA^=QKF4}N+vVb_yWKe_hNR}XExni%o*!{_2l0)1LHA2vO380|54 zH8;%LbS?9$=ijG2byLy$5uAwGmJ|P{>v@64|2^X8sG~I(yg$D@rMabPbDsNK0h!NV z{-nI*FKZ{S^w|ILfy?_(h3Q{4uHJMiNgEQUD_h=dtY3XPYnsR0g5~bNFnf+|gSnU% zUK@3_|G53OiH$#UJN%}b7SA>{A2MU7G}AW%Qau@-x*~=CvsD3TC(2iK6G}~!TGDJ*R7Zy=Jxa;!-cow zK8Sn&$L>tiys>u-KO8k~PH#J)<}w$*ljT>xa`A;z?e)i7Z*1Ch&eu2jMA&ot!*|Vj zXM*uh{mWbYYL0!Hb>xZcyes=&y&h4%@2XN?e!6S($A3oymiQqiRe$K+$}{C&x{!Yj zl0E)rz}CdWXt%j5{_LFeuW&pVlS zCe6cr!);ygbKhUjx_n}p?uMb_dpK|YS9V*_;?w7@-1=(9#>S=vemCo%o&Wf8)2mm? zjfdXIe)IiB0}i}6d*y^9T3z?cI@6bLwYEI+-CheCwlQwfiABfT-uSNNVtD459gKtRk1FxZ}nr+S7Oh1(@3~v2y?fR>oQ!}IDm+`gotSq!?Ga0z|;dSVaDpkLPf4RxKg^sqGY)!LOLBCz=6)))%$ zI8T5!G`AP|aW5>J7=V`oLrb)^k1WxYX;bty8eLJorb?TiYcS|!+;c`h&TRA^cvsGH zpBY1BAvirKu=U*5x&S4sgq~nyDQq?T!z{0^?6X_%aC z@Zut#_2rDlkut=sGmZ}lCQawSg`S71;A+xCrP0A2t~5&U8GvFf_oXpVHpF%gvwi3} zmxx-2b11)rAq}`s0EydEoEBDk_#BJh1Y2^2UYT#G&SeF@B%RXkjUnI!hT}qkhqog`$9#m$ zj3znhZRh$5$15+DWly@L0<_$P3r7#t^K9IsF=F zFLtZ~pA9Sa@mSvuy@7T?C3Lh5@nAGKBDBuo(FpDhV-?!ou^OMD>AG~A6)OXTT}js% z!qvylwVQHXK#{Oj|53q4S7DAN)M*)rC0w-$KXf>975Hpiv5)8a;q`aXCa9N=mLVR@ z21kU}IXxP|y& z8eCx?OV|&yvCj#o>~nC(umoAU<`dXHb}gsl2>U$cfDY1$pactiHul)Zv;SMr`-fhCr#Z_ts%k=YQ3v@{i2Jdv zxdc8NRq*jt&z$)hRY93`v<&fJGB_f%&e_oj?hR8F+TN))uryiAm9}K9L@r@f6Racb zSlcPq5|(1E*ZVB{Hu=x4VVy*Zz`}d17X&^VR_x=8z}c^=&=M%Kj+P-F%mzn<);T>I z!M$OwLfZ$fODR`y?46%-dq)Ued{x{$NDxZ zl6*#($Bu8H-PrS0;Io0nJ|673U)G~csG*LQAs!3|M}*cnJ{rNjVX#8m2e8{H*kW=g zBXs~61K0^+Fr%S;Sl~vXNbZF3ewm*g?QM#-JcmGGOjb3eKSrofd!uxEUaK-ppbSsSO5zig51G6$44W$Hw;#2dj-ohdf`gIu-UeJ1;(xw0u=!p zU7y$1$c82YzzS#g?^;H2-XAwpA9Vdc(C7h#-mWkNk_{N4~ByyLhBqK zjo{ueSfT9$SS2m7^X>PpY*$k@hL9a?C%c!DU3!*J+4E2eEOr?@!f}D5*I%*i8yyWbv%DD z4}s5r(GJ0S{>7pI^A~OI*7GmsAxQO4+zZf}|6(PfRR8c*C|(V)o`10pL8^b`{ELMj z?*C$4`E*=T|3y3mYyOLM2vYr%>K|VLM5zHZ|HaDwHYBb8#Ju~{&wpYrK&1L7)jz5J ziB|)p^`EGZU!~`N;^J=$?4|ffPwDXql0K#Nk|s2Zf3+1nUZsn-M(5AJh=*YPlrGjG zNKdKw{1+(+rB#Z!w@K+!8p(f=vLCfc^$+L2X!xb4RN~?%zoj7NAxQO4s(<_nNR%=} z>%Ub0#Hs<(=U*|iU-~UIY5gbGl~2c&{*_YH<+rr{6Bobr^N(l?kicGUKhjfrT&JW} zN-t@GA<=^VtGvQ@Y0)kb7@NOKiFOFmD&<#9u0G_Jv`Xm%q+jLAr{h}lU#tsIdP)W7 zLBtYlJ^x}I0$l%Mr5+iV&R@Jkkm{e9*)RPrDb9cKvR_*NN%fE1|3phd>9;iK;!>;{ zAg%vM{)>fQ`YjD{@mrsNNcB&oGGzVyBi02d)jx4Bz(9Wa;p_rjd34LFqg!Mk1|$b5 zKyDy+kO!zAs6WUPbRTE{Xdq}1XfVhNGz8=g8Vd3O4FmathJ!|cMuPl6qd@+k0MKYq zAV>+iA2bFO1PTU)fPM!W3kn5|1C0kw0EL0VK@p&dph!>@C>o>!#eiZ#aiDmR8Z-$s z8T5P56wp-AG*AMF1to%#K+{1pKo5W(1SNwW0;PabK@Wpwg3>^Sfigi^phcj?pzQXeTXJg3xE<5OxYwqSv1-GN!J2@POybYFq6cT- zuQ>|7>l^&xEB5_X<+m4sire$sOHL+Qzv18Z75ostRRYqr=ie%=+eiQ7ntnS!bnK9R omp)>2a9w;BA1r>k_K}*_O2N(TmXZzXR`X}f959N3BbN~PV delta 6894 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 O47gfJ&p}~to&7Jt!*i_w