2
0

Add support for Answers and improved Fabric integration.

This commit is contained in:
Maarten Billemont
2017-04-29 23:03:50 -04:00
parent 07e55140ac
commit 6d9be3fdfe
26 changed files with 237 additions and 129 deletions

View File

@@ -93,7 +93,7 @@ NSOperationQueue *_mpwQueue = nil;
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
NSArray *migrationSites = [moc executeFetchRequest:migrationRequest error:&error];
if (!migrationSites) {
err( @"While looking for sites to migrate: %@", [error fullDescription] );
MPError( error, @"While looking for sites to migrate." );
return NO;
}

View File

@@ -29,7 +29,7 @@
@protocol MPInAppDelegate
- (void)updateWithProducts:(NSArray /* SKProduct */ *)products;
- (void)updateWithProducts:(NSDictionary<NSString *, SKProduct *> *)products;
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction;
@end

View File

@@ -23,7 +23,8 @@
@implementation MPAppDelegate_Shared(InApp)
PearlAssociatedObjectProperty( NSArray*, Products, products );
PearlAssociatedObjectProperty( NSDictionary*, Products, products );
PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObservers );
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate {
@@ -101,11 +102,25 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
}
#endif
for (SKProduct *product in self.products)
for (SKProduct *product in [self.products allValues])
if ([product.productIdentifier isEqualToString:productIdentifier]) {
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.quantity = quantity;
[[self paymentQueue] addPayment:payment];
if (payment) {
payment.quantity = quantity;
[[self paymentQueue] addPayment:payment];
if ([[MPConfig get].sendInfo boolValue]) {
#ifdef CRASHLYTICS
[Answers logAddToCartWithPrice:product.price currency:product.priceLocale.currencyCode itemName:product.localizedTitle
itemType:@"InApp" itemId:product.productIdentifier
customAttributes:nil];
[Answers logStartCheckoutWithPrice:product.price currency:product.priceLocale.currencyCode itemCount:@(quantity)
customAttributes:@{
@"products": @[ productIdentifier ],
}];
#endif
}
}
return;
}
}
@@ -114,8 +129,13 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
inf( @"products: %@, invalid: %@", response.products, response.invalidProductIdentifiers );
self.products = response.products;
if ([response.invalidProductIdentifiers count])
inf( @"Invalid products: %@", response.invalidProductIdentifiers );
NSMutableDictionary *products = [NSMutableDictionary dictionaryWithCapacity:[response.products count]];
for (SKProduct *product in response.products)
products[product.productIdentifier] = product;
self.products = products;
for (id<MPInAppDelegate> productObserver in self.productObservers)
[productObserver updateWithProducts:self.products];
@@ -123,6 +143,8 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
MPError( error, @"StoreKit request (%@) failed.", request );
#if TARGET_OS_IPHONE
[PearlAlert showAlertWithTitle:@"Purchase Failed" message:
strf( @"%@\n\n%@", error.localizedDescription,
@@ -131,7 +153,6 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
cancelTitle:@"OK" otherTitles:nil];
#else
#endif
err( @"StoreKit request (%@) failed: %@", request, [error fullDescription] );
}
- (void)requestDidFinish:(SKRequest *)request {
@@ -147,21 +168,37 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, (int)(transaction.transactionState) );
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
inf( @"purchased: %@", transaction.payment.productIdentifier );
inf( @"Purchased: %@", transaction.payment.productIdentifier );
NSMutableDictionary *attributes = [NSMutableDictionary new];
if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) {
float currentFuel = [[MPiOSConfig get].developmentFuelRemaining floatValue];
float purchasedFuel = transaction.payment.quantity / MP_FUEL_HOURLY_RATE;
[MPiOSConfig get].developmentFuelRemaining = @(currentFuel + purchasedFuel);
if (![MPiOSConfig get].developmentFuelChecked || currentFuel < DBL_EPSILON)
[MPiOSConfig get].developmentFuelChecked = [NSDate date];
[attributes addEntriesFromDictionary:@{
@"currentFuel" : @(currentFuel),
@"purchasedFuel": @(purchasedFuel),
}];
}
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
if ([[MPConfig get].sendInfo boolValue]) {
#ifdef CRASHLYTICS
SKProduct *product = self.products[transaction.payment.productIdentifier];
[Answers logPurchaseWithPrice:product.price currency:product.priceLocale.currencyCode success:@YES
itemName:product.localizedTitle itemType:@"InApp" itemId:product.productIdentifier
customAttributes:attributes];
#endif
}
break;
}
case SKPaymentTransactionStateRestored: {
inf( @"restored: %@", transaction.payment.productIdentifier );
inf( @"Restored: %@", transaction.payment.productIdentifier );
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
@@ -173,6 +210,18 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
case SKPaymentTransactionStateFailed:
err( @"Transaction failed: %@, reason: %@", transaction.payment.productIdentifier, [transaction.error fullDescription] );
[queue finishTransaction:transaction];
if ([[MPConfig get].sendInfo boolValue]) {
#ifdef CRASHLYTICS
SKProduct *product = self.products[transaction.payment.productIdentifier];
[Answers logPurchaseWithPrice:product.price currency:product.priceLocale.currencyCode success:@YES
itemName:product.localizedTitle itemType:@"InApp" itemId:product.productIdentifier
customAttributes:@{
@"state" : @"Failed",
@"reason": [transaction.error fullDescription],
}];
#endif
}
break;
}
@@ -185,7 +234,7 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
err( @"StoreKit restore failed: %@", [error fullDescription] );
MPError( error, @"StoreKit restore failed." );
}
@end

View File

@@ -16,12 +16,13 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import <Crashlytics/Answers.h>
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
@interface MPAppDelegate_Shared()
@property(strong, nonatomic) MPKey *key;
@property(strong, atomic) MPKey *key;
@end
@@ -177,6 +178,14 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
else
dbg( @"Automatic login failed for user: %@", user.userID );
if ([[MPConfig get].sendInfo boolValue]) {
#ifdef CRASHLYTICS
[Answers logLoginWithMethod:password? @"Password": @"Automatic" success:@NO customAttributes:@{
@"algorithm": @(user.algorithm.version),
}];
#endif
}
return NO;
}
inf( @"Logged in user: %@", user.userID );
@@ -198,8 +207,11 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
@try {
if ([[MPConfig get].sendInfo boolValue]) {
#ifdef CRASHLYTICS
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
[[Crashlytics sharedInstance] setUserName:user.userID];
[Answers logLoginWithMethod:password? @"Password": @"Automatic" success:@YES customAttributes:@{
@"algorithm": @(user.algorithm.version),
}];
#endif
}
}

View File

@@ -26,9 +26,9 @@
#endif
@property(strong, nonatomic, readonly) MPKey *key;
@property(strong, nonatomic, readonly) NSManagedObjectID *activeUserOID;
@property(strong, nonatomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;
@property(strong, atomic, readonly) MPKey *key;
@property(strong, atomic, readonly) NSManagedObjectID *activeUserOID;
@property(strong, atomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;
+ (instancetype)get;

View File

@@ -23,9 +23,9 @@
@interface MPAppDelegate_Shared()
@property(strong, nonatomic) MPKey *key;
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
@property(strong, nonatomic) NSPersistentStoreCoordinator *storeCoordinator;
@property(strong, atomic) MPKey *key;
@property(strong, atomic) NSManagedObjectID *activeUserOID;
@property(strong, atomic) NSPersistentStoreCoordinator *storeCoordinator;
@end
@@ -76,12 +76,13 @@
NSError *error;
if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error])
err( @"Failed to obtain a permanent object ID after setting active user: %@", [error fullDescription] );
MPError( error, @"Failed to obtain a permanent object ID after setting active user." );
self.activeUserOID = activeUser.objectID;
}
- (void)handleCoordinatorError:(NSError *)error {
}
@end

View File

@@ -237,7 +237,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSURL *localStoreURL = [self localStoreURL];
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
err( @"Couldn't create our application support directory: %@", [error fullDescription] );
MPError( error, @"Couldn't create our application support directory." );
return;
}
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
@@ -246,7 +246,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
err( @"Failed to open store: %@", [error fullDescription] );
MPError( error, @"Failed to open store." );
self.storeCorrupted = @YES;
[self handleCoordinatorError:error];
return;
@@ -255,12 +255,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
#if TARGET_OS_IPHONE
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
#else
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
#endif
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
[self.mainManagedObjectContext saveToStore];
} );
#endif
// Perform a data sanity check on the newly loaded store to find and fix any issues.
if ([[MPConfig get].checkInconsistency boolValue])
@@ -286,10 +289,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSError *error = nil;
for (NSPersistentStore *store in self.storeCoordinator.persistentStores) {
if (![self.storeCoordinator removePersistentStore:store error:&error])
err( @"Couldn't remove persistence store from coordinator: %@", [error fullDescription] );
MPError( error, @"Couldn't remove persistence store from coordinator." );
}
if (![[NSFileManager defaultManager] removeItemAtURL:self.localStoreURL error:&error])
err( @"Couldn't remove persistence store at URL %@: %@", self.localStoreURL, [error fullDescription] );
MPError( error, @"Couldn't remove persistence store at URL %@.", self.localStoreURL );
[self loadStore];
}
@@ -307,7 +310,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
fetchRequest.entity = entity;
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
if (!objects) {
err( @"Failed to fetch %@ objects: %@", entity, [error fullDescription] );
MPError( error, @"Failed to fetch %@ objects.", entity );
continue;
}
@@ -332,7 +335,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
- (void)migrateStore {
MPStoreMigrationLevel migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
MPStoreMigrationLevel migrationLevel = (MPStoreMigrationLevel)
[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
if (migrationLevel >= MPStoreMigrationLevelCurrent)
// Local store up-to-date.
return;
@@ -375,7 +379,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
if (![NSPersistentStore migrateStore:oldLocalStoreURL withOptions:@{ STORE_OPTIONS }
toStore:newLocalStoreURL withOptions:@{ STORE_OPTIONS }
model:nil error:&error]) {
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
MPError( error, @"Couldn't migrate the old store to the new location." );
return NO;
}
@@ -422,7 +426,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} model:nil error:&error]) {
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
MPError( error, @"Couldn't migrate the old store to the new location." );
return NO;
}
@@ -459,7 +463,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSError *error = nil;
if (site.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ site ] error:&error])
err( @"Failed to obtain a permanent object ID after creating new site: %@", [error fullDescription] );
MPError( error, @"Failed to obtain a permanent object ID after creating new site." );
[context saveToStore];
@@ -491,7 +495,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSError *error = nil;
if (![context obtainPermanentIDsForObjects:@[ newSite ] error:&error])
err( @"Failed to obtain a permanent object ID after changing object type: %@", [error fullDescription] );
MPError( error, @"Failed to obtain a permanent object ID after changing object type." );
[context deleteObject:site];
[context saveToStore];
@@ -537,7 +541,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:(NSRegularExpressionOptions)0 error:&error];
if (error) {
err( @"Error loading the header pattern: %@", [error fullDescription] );
MPError( error, @"Error loading the header pattern." );
return MPImportResultInternalError;
}
}
@@ -551,7 +555,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
options:(NSRegularExpressionOptions)0 error:&error]
];
if (error) {
err( @"Error loading the site patterns: %@", [error fullDescription] );
MPError( error, @"Error loading the site patterns." );
return MPImportResultInternalError;
}
}
@@ -610,7 +614,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
if (!users) {
err( @"While looking for user: %@, error: %@", importUserName, [error fullDescription] );
MPError( error, @"While looking for user: %@.", importUserName );
return MPImportResultInternalError;
}
if ([users count] > 1) {
@@ -694,7 +698,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
if (!existingSites) {
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, [error fullDescription] );
MPError( error, @"Lookup of existing sites failed for site: %@, user: %@.", siteName, user.userID );
return MPImportResultInternalError;
}
if ([existingSites count]) {

View File

@@ -20,7 +20,7 @@
[self.defaults registerDefaults:@{
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
NSStringFromSelector( @selector( sendInfo ) ) : @YES,
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
NSStringFromSelector( @selector( checkInconsistency ) ): @NO,

View File

@@ -21,11 +21,11 @@
@try {
NSError *error = nil;
if (!(success = [self save:&error]))
err( @"While saving: %@", [error fullDescription] );
MPError( error, @"While saving." );
}
@catch (NSException *exception) {
success = NO;
err( @"While saving: %@", [exception fullDescription] );
err( @"While saving.\n%@", [exception fullDescription] );
}
}];

View File

@@ -6,6 +6,9 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Crashlytics/Crashlytics.h>
#import <Crashlytics/Answers.h>
__BEGIN_DECLS
extern NSString *const MPErrorDomain;
@@ -19,4 +22,21 @@ extern NSString *const MPFoundInconsistenciesNotification;
extern NSString *const MPSitesImportedNotificationUserKey;
extern NSString *const MPInconsistenciesFixResultUserKey;
__END_DECLS
#ifdef CRASHLYTICS
#define MPError(error_, message, ...) ({ \
err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \
\
if ([[MPConfig get].sendInfo boolValue]) { \
[[Crashlytics sharedInstance] recordError:error_ withAdditionalUserInfo:@{ \
@"location": strf( @"%@:%d %@", @(basename((char *)__FILE__)), __LINE__, NSStringFromSelector(_cmd) ), \
}]; \
} \
})
#else
#define MPError(error_, ...) ({ \
err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \
})
#endif

View File

@@ -78,7 +78,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
[[Crashlytics sharedInstance] setUserIdentifier:[PearlKeyChain deviceIdentifier]];
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
[[Crashlytics sharedInstance] setUserName:@"Anonymous"];
[[Crashlytics sharedInstance] setObjectValue:@"Anonymous" forKey:@"username"];
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelInfo;
@@ -272,7 +271,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
if (error)
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
MPError( error, @"While reading imported sites from %@.", url );
if (!importedSitesData)
return;
@@ -394,9 +393,9 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
inManagedObjectContext:moc];
newUser.name = name;
[moc saveToStore];
NSError *error = nil;
if (![moc obtainPermanentIDsForObjects:@[ newUser ] error:&error])
err( @"Failed to obtain permanent object ID for new user: %@", [error fullDescription] );
// NSError *error = nil;
// if (![moc obtainPermanentIDsForObjects:@[ newUser ] error:&error])
// MPError( error, @"Failed to obtain permanent object ID for new user." );
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateUsers];
@@ -516,20 +515,23 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
NSError *coordinateError = nil;
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
[[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError
byAccessor:^(NSURL *newURL) {
NSError *writeError = nil;
if (![exportedSites writeToURL:newURL atomically:NO
encoding:NSUTF8StringEncoding
error:&writeError])
PearlMainQueue( ^{
[[NSAlert alertWithError:writeError] runModal];
} );
}];
if (coordinateError)
[[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:savePanel.URL options:0
error:&coordinateError byAccessor:
^(NSURL *newURL) {
NSError *writeError = nil;
if (![exportedSites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
MPError( writeError, @"Could not write to the export file." );
PearlMainQueue( ^{
[[NSAlert alertWithError:writeError] runModal];
} );
}];
if (coordinateError) {
MPError( coordinateError, @"Write access to the export file could not be obtained." );
PearlMainQueue( ^{
[[NSAlert alertWithError:coordinateError] runModal];
} );
}
}
- (void)updateUsers {
@@ -567,7 +569,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
if (!users)
err( @"Failed to load users: %@", [error fullDescription] );
MPError( error, @"Failed to load users." );
if (![users count]) {
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];

View File

@@ -555,7 +555,7 @@
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
if (!siteResults) {
prof_finish( @"executeFetchRequest: %@ // %@", fetchRequest.predicate, [error fullDescription] );
err( @"While fetching sites for completion: %@", [error fullDescription] );
MPError( error, @"While fetching sites for completion." );
return;
}
prof_rewind( @"executeFetchRequest: %@", fetchRequest.predicate );

View File

@@ -115,7 +115,7 @@
NSError *error;
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:_entityOID error:&error];
if (!entity)
err( @"Couldn't retrieve active site: %@", [error fullDescription] );
MPError( error, @"Couldn't retrieve active site." );
return entity;
}

View File

@@ -333,12 +333,12 @@
question.keyword = keyword;
if ([context saveToStore]) {
if ([question.objectID isTemporaryID]) {
NSError *error = nil;
[context obtainPermanentIDsForObjects:@[ question ] error:&error];
if (error)
err( @"Failed to obtain permanent object ID: %@", [error fullDescription] );
}
// if ([question.objectID isTemporaryID]) {
// NSError *error = nil;
// [context obtainPermanentIDsForObjects:@[ question ] error:&error];
// if (error)
// MPError( error, @"Failed to obtain permanent object ID: %@" );
// }
_questionOID = question.objectID;
[self updateAnswerForQuestion:question ofSite:site];

View File

@@ -395,7 +395,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
self.fetchedResultsController.fetchRequest.predicate =
[NSPredicate predicateWithFormat:@"name LIKE[cd] %@ AND user == %@", queryPattern, [MPiOSAppDelegate get].activeUserOID];
if (![self.fetchedResultsController performFetch:&error])
err( @"Couldn't fetch sites: %@", [error fullDescription] );
MPError( error, @"Couldn't fetch sites." );
PearlMainQueue( ^{
[self.passwordCollectionView updateDataSource:_passwordCollectionSections

View File

@@ -39,6 +39,8 @@
@interface MPStoreProductCell : UITableViewCell
@property(nonatomic) IBOutlet UILabel *priceLabel;
@property(nonatomic) IBOutlet UILabel *titleLabel;
@property(nonatomic) IBOutlet UILabel *descriptionLabel;
@property(nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
@property(nonatomic) IBOutlet UIView *purchasedIndicator;

View File

@@ -28,7 +28,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
@interface MPStoreViewController()<MPInAppDelegate>
@property(nonatomic, strong) NSNumberFormatter *currencyFormatter;
@property(nonatomic, strong) NSArray *products;
@property(nonatomic, strong) NSDictionary<NSString *, SKProduct *> *products;
@end
@@ -43,7 +43,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
];
NSInteger storeVersion = [[NSUserDefaults standardUserDefaults] integerForKey:@"storeVersion"];
for (; storeVersion < [storeVersions count]; ++storeVersion)
[features appendFormat:@"%@\n", storeVersions[storeVersion]];
[features appendFormat:@"%@\n", storeVersions[(NSUInteger)storeVersion]];
if (![features length])
return nil;
@@ -170,7 +170,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
#pragma mark - MPInAppDelegate
- (void)updateWithProducts:(NSArray *)products {
- (void)updateWithProducts:(NSDictionary<NSString *, SKProduct *> *)products {
self.products = products;
@@ -218,7 +218,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
- (SKProduct *)productForCell:(MPStoreProductCell *)cell {
for (SKProduct *product in self.products)
for (SKProduct *product in [self.products allValues])
if ([self cellForProductIdentifier:product.productIdentifier] == cell)
return product;
@@ -248,7 +248,7 @@ PearlEnum( MPDevelopmentFuelConsumption,
[hideCells addObjectsFromArray:[self.allCellsBySection[0] array]];
[hideCells addObject:self.loadingCell];
for (SKProduct *product in self.products) {
for (SKProduct *product in [self.products allValues]) {
[self showCellForProductWithIdentifier:MPProductGenerateLogins ifProduct:product showingCells:showCells];
[self showCellForProductWithIdentifier:MPProductGenerateAnswers ifProduct:product showingCells:showCells];
[self showCellForProductWithIdentifier:MPProductOSIntegration ifProduct:product showingCells:showCells];
@@ -313,6 +313,8 @@ PearlEnum( MPDevelopmentFuelConsumption,
BOOL purchased = [[MPiOSAppDelegate get] isFeatureUnlocked:productIdentifier];
NSInteger quantity = [self quantityForProductIdentifier:productIdentifier];
cell.priceLabel.text = purchased? @"": [self.currencyFormatter stringFromNumber:@([product.price floatValue] * quantity)];
cell.titleLabel.text = product.localizedTitle;
cell.descriptionLabel.text = product.localizedDescription;
cell.purchasedIndicator.visible = purchased;
}

View File

@@ -16,6 +16,7 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import <Crashlytics/Answers.h>
#import "MPUsersViewController.h"
#import "MPEntities.h"
#import "MPAvatarCell.h"
@@ -224,6 +225,17 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
user.defaultType = user.algorithm.defaultType;
user.avatar = newUserAvatar;
user.name = newUserName;
if ([[MPConfig get].sendInfo boolValue]) {
#ifdef CRASHLYTICS
[Answers logSignUpWithMethod:@"Manual"
success:@YES
customAttributes:@{
@"algorithm": @(user.algorithm.version),
@"avatar" : @(user.avatar),
}];
#endif
}
}
BOOL signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context
@@ -719,7 +731,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
];
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
if (!users) {
err( @"Failed to load users: %@", [error fullDescription] );
MPError( error, @"Failed to load users." );
self.userIDs = nil;
}

View File

@@ -25,7 +25,6 @@
@property(nonatomic, strong) UIDocumentInteractionController *interactionController;
@property(nonatomic) UIBackgroundTaskIdentifier task;
@end
@implementation MPiOSAppDelegate
@@ -59,10 +58,9 @@
[[Crashlytics sharedInstance] setUserIdentifier:[PearlKeyChain deviceIdentifier]];
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
[[Crashlytics sharedInstance] setUserName:@"Anonymous"];
[[Crashlytics sharedInstance] setObjectValue:@"Anonymous" forKey:@"username"];
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelInfo;
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPConfig get].sendInfo boolValue])
level = PearlLogLevelDebug;
@@ -83,9 +81,6 @@
PearlAddNotificationObserver( MPCheckConfigNotification, nil, [NSOperationQueue mainQueue], ^(id self, NSNotification *note) {
[self updateConfigKey:note.object];
} );
// PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) {
// [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
// } );
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, nil, ^(id self, NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
} );
@@ -155,7 +150,8 @@
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
if (error)
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
MPError( error, @"While reading imported sites from %@.", url );
if (!importedSitesData) {
[PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@",
[error localizedDescription]?: error )];
@@ -483,7 +479,7 @@
NSError *error = nil;
if (![[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&error])
err( @"Failed to write export data to URL %@: %@", exportURL, [error fullDescription] );
MPError( error, @"Failed to write export data to URL %@.", exportURL );
else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
@@ -574,7 +570,7 @@
static NSDictionary *crashlyticsInfo = nil;
if (crashlyticsInfo == nil)
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
[[NSBundle mainBundle] URLForResource:@"Fabric" withExtension:@"plist"]];
return crashlyticsInfo;
}

View File

@@ -60,7 +60,7 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>© 2011-2016 Lyndir</string>
<string>© 2011-2017</string>
<key>UIAppFonts</key>
<array>
<string>Exo2.0-Bold.otf</string>

View File

@@ -6,7 +6,7 @@
<array>
<dict>
<key>FooterText</key>
<string>Enable this setting to send us carefully anonymized information to help us diagnose and resolve issues you might experience in the future.</string>
<string>Enable this setting to send carefully anonymized information to help us diagnose and resolve any issues you encounter in a future update.</string>
<key>Title</key>
<string></string>
<key>Type</key>
@@ -50,7 +50,7 @@
<key>Key</key>
<string>sendInfo</string>
<key>DefaultValue</key>
<false/>
<true/>
</dict>
<dict>
<key>FooterText</key>

View File

@@ -2620,8 +2620,10 @@ See </string>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="activityIndicator" destination="cef-sc-aph" id="VmL-AX-R3f"/>
<outlet property="descriptionLabel" destination="Ra0-yS-99P" id="99B-ao-lE6"/>
<outlet property="priceLabel" destination="68f-wn-UlS" id="e99-ys-HtZ"/>
<outlet property="purchasedIndicator" destination="FWu-V6-mLT" id="Zqt-GG-e5v"/>
<outlet property="titleLabel" destination="Pax-1J-IZi" id="kVH-n6-mDs"/>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" shouldIndentWhileEditing="NO" reuseIdentifier="MPStoreProductCellGenerateAnswers" id="l1g-Ul-Vg8" userLabel="Generate Answers" customClass="MPStoreProductCell">
@@ -2685,8 +2687,10 @@ See </string>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="activityIndicator" destination="X2g-Go-2Hz" id="PvQ-bP-exW"/>
<outlet property="descriptionLabel" destination="yRH-27-edZ" id="pfv-na-UM3"/>
<outlet property="priceLabel" destination="9ct-IM-QKR" id="UTQ-L7-vbu"/>
<outlet property="purchasedIndicator" destination="N9y-ue-L8d" id="Ppv-ay-M1J"/>
<outlet property="titleLabel" destination="Kkl-gT-YAO" id="cHy-iU-oHr"/>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" shouldIndentWhileEditing="NO" reuseIdentifier="MPStoreProductCellOSIntegration" id="9Na-CL-jBq" userLabel="iOS Integration" customClass="MPStoreProductCell">
@@ -2750,8 +2754,10 @@ See </string>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="activityIndicator" destination="yUe-TX-fli" id="DH6-pK-fse"/>
<outlet property="descriptionLabel" destination="riF-bB-x5g" id="3v9-6H-PAK"/>
<outlet property="priceLabel" destination="3jH-eX-9N2" id="agT-az-dVU"/>
<outlet property="purchasedIndicator" destination="ec8-P9-KPY" id="M39-bc-Ksp"/>
<outlet property="titleLabel" destination="Zch-DS-J3I" id="cY2-FQ-q69"/>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" shouldIndentWhileEditing="NO" reuseIdentifier="MPStoreProductCellTouchID" id="8en-6R-GvR" userLabel="TouchID" customClass="MPStoreProductCell">
@@ -2815,8 +2821,10 @@ See </string>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="activityIndicator" destination="Dv5-t7-lL1" id="AFu-Dd-TgU"/>
<outlet property="descriptionLabel" destination="Yxc-Lr-382" id="47I-83-lgP"/>
<outlet property="priceLabel" destination="ZGg-O6-rsg" id="dAn-xu-gut"/>
<outlet property="purchasedIndicator" destination="yZX-ns-8oV" id="7x0-eq-oSs"/>
<outlet property="titleLabel" destination="e1D-jp-GBs" id="jFw-dm-vp6"/>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" shouldIndentWhileEditing="NO" reuseIdentifier="MPStoreProductCellFuel" id="le3-Q5-MSO" userLabel="Fuel" customClass="MPStoreProductCell">
@@ -2908,7 +2916,9 @@ invested: 3.7 work hours</string>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="activityIndicator" destination="eS4-59-Xny" id="kGW-fn-VqH"/>
<outlet property="descriptionLabel" destination="fz2-AO-aGW" id="xkc-xV-Z6Y"/>
<outlet property="priceLabel" destination="EbU-DV-fKF" id="pg2-8o-7We"/>
<outlet property="titleLabel" destination="Jnv-uN-xeg" id="Kxi-RE-ipa"/>
</connections>
</tableViewCell>
</cells>