2
0

Some usability improvements + apptentive fixes.

[IMPROVED]  Make persistence more lazy to avoid UI blocks.
[IMPROVED]  Use "Master Password" as CFBundleDisplayName at runtime.  No
            home-screen length restrictions there.
[FIXED]     Inform Apptentive of significant events.
This commit is contained in:
Maarten Billemont
2012-05-27 14:26:13 +02:00
parent ac14b10752
commit 7d0ea4b3f5
11 changed files with 170 additions and 119 deletions

View File

@@ -42,28 +42,37 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
if (managedObjectContext)
return managedObjectContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
assert(coordinator);
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext performBlockAndWait:^{
managedObjectContext.persistentStoreCoordinator = coordinator;
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
return [PearlLazy lazyObjectLoadedFrom:^id{
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
assert(coordinator);
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext performBlockAndWait:^{
managedObjectContext.persistentStoreCoordinator = coordinator;
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
}];
return managedObjectContext;
}];
return managedObjectContext;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// Start loading the store.
[self storeManager];
// Wait until the storeManager is ready.
for(__block BOOL isReady = [self storeManager].isReady; !isReady;)
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
isReady = [self storeManager].isReady;
});
assert([self storeManager].isReady);
return [self storeManager].persistentStoreCoordinator;
return [PearlLazy lazyObjectLoadedFrom:^id{
// Wait until the storeManager is ready.
for(__block BOOL isReady = [self storeManager].isReady; !isReady;) {
[NSThread sleepForTimeInterval:0.1];
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
isReady = [self storeManager].isReady;
});
}
assert([self storeManager].isReady);
return [self storeManager].persistentStoreCoordinator;
}];
}
- (UbiquityStoreManager *)storeManager {

View File

@@ -148,6 +148,8 @@
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
#ifndef DEBUG
@try {
@@ -207,20 +209,39 @@
}
#endif
});
@try {
NSString *apptentiveAPIKey = [self apptentiveAPIKey];
if ([apptentiveAPIKey length]) {
dbg(@"Initializing Apptentive");
ATConnect *connection = [ATConnect sharedConnection];
connection.shouldTakeScreenshot = NO;
connection.apiKey = apptentiveAPIKey;
[connection setApiKey:apptentiveAPIKey];
[connection setShouldTakeScreenshot:NO];
[connection addAdditionalInfoToFeedback:[PearlInfoPlist get].CFBundleVersion withKey:@"CFBundleVersion"];
ATAppRatingFlow *ratingsFlow = [ATAppRatingFlow sharedRatingFlowWithAppID:[PearlConfig get].iTunesID];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:nil
usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
[ratingsFlow appDidEnterForeground:YES
viewController:self.navigationController];
});
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeySet object:nil queue:nil
usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
[ratingsFlow userDidPerformSignificantEvent:YES
viewController:self.navigationController];
});
}];
[ratingsFlow appDidLaunch:YES viewController:self.navigationController];
}
}
@catch (NSException *exception) {
err(@"Apptentive: %@", exception);
}
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone];
@@ -288,19 +309,11 @@
@"https://youtrack.lyndir.com\n"
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#else
@try {
ATAppRatingFlow *sharedFlow = [ATAppRatingFlow sharedRatingFlowWithAppID:[PearlConfig get].iTunesID];
[sharedFlow appDidLaunch:YES viewController:self.navigationController];
}
@catch (NSException *exception) {
err(@"Apptentive: %@", exception);
}
#endif
[[UIApplication sharedApplication] setStatusBarHidden:NO
withAnimation:UIStatusBarAnimationSlide];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@@ -378,9 +391,6 @@
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
ATAppRatingFlow *sharedFlow = [ATAppRatingFlow sharedRatingFlowWithAppID:[PearlConfig get].iTunesID];
[sharedFlow appDidEnterForeground:YES viewController:self.navigationController];
[super applicationDidBecomeActive:application];
}
@@ -484,7 +494,7 @@
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
return;
}
[MPConfig get].iCloudDecided = [NSNumber numberWithBool:YES];
if (buttonIndex == [alert cancelButtonIndex])
return;
@@ -541,7 +551,7 @@
static NSDictionary *apptentiveInfo = nil;
if (apptentiveInfo == nil)
apptentiveInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Apptentive" withExtension:@"plist"]];
[[NSBundle mainBundle] URLForResource:@"Apptentive" withExtension:@"plist"]];
return apptentiveInfo;
}

View File

@@ -146,8 +146,6 @@
- (void)updateAnimated:(BOOL)animated {
[[MPAppDelegate get] saveContext];
dispatch_async(dispatch_get_main_queue(), ^{
if (animated)
[UIView animateWithDuration:0.3f animations:^{
@@ -352,6 +350,7 @@
NSString *oldPassword = [self.activeElement.content description];
task();
NSString *newPassword = [self.activeElement.content description];
[[MPAppDelegate get] saveContext];
[self updateAnimated:YES];
// Show new and old password.

View File

@@ -14,7 +14,6 @@
@interface MPSearchDelegate (Private)
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath;
- (void)update;
@end
@@ -38,10 +37,14 @@
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
self.fetchedResultsController = [PearlLazy lazyObjectLoadedFrom:^id{
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil];
controller.delegate = self;
return controller;
}];
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
self.tipView.textAlignment = UITextAlignmentCenter;
@@ -123,12 +126,8 @@
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self update];
return NO;
}
- (void)update {
if (!controller.active)
return NO;
assert(self.query);
@@ -138,7 +137,6 @@
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
err(@"Couldn't fetch elements: %@", error);
[self.searchDisplayController.searchResultsTableView reloadData];
NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews;
NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1;
@@ -149,6 +147,8 @@
[self.tipView removeFromSuperview];
[overlay addSubview:self.tipView];
}
return YES;
}
// See MP-14, also crashes easily on internal assertions etc..

View File

@@ -152,73 +152,69 @@ typedef enum {
- (void)textFieldDidEndEditing:(UITextField *)textField {
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.toValue = [NSNumber numberWithFloat:2 * M_PI];
rotate.repeatCount = MAXFLOAT;
rotate.duration = 3.0;
[self.spinner.layer removeAllAnimations];
[self.spinner.layer addAnimation:rotate forKey:@"transform"];
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 1.0f;
}];
[self showMessage:@"Checking password..." state:MPLockscreenProgress];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@try {
dispatch_async(dispatch_get_main_queue(), ^{
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.toValue = [NSNumber numberWithFloat:2 * M_PI];
rotate.repeatCount = MAXFLOAT;
rotate.duration = 3.0;
BOOL unlocked = [[MPAppDelegate get] tryMasterPassword:textField.text];
dispatch_async(dispatch_get_main_queue(), ^{
if (unlocked) {
[self showMessage:@"Success!" state:MPLockscreenSuccess];
[self.spinner.layer removeAllAnimations];
[self.spinner.layer addAnimation:rotate forKey:@"transform"];
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 1.0f;
}];
[self showMessage:@"Checking password..." state:MPLockscreenProgress];
});
if ([[MPAppDelegate get] tryMasterPassword:textField.text])
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Success!" state:MPLockscreenSuccess];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", [MPAppDelegate get].keyID];
fetchRequest.fetchLimit = 1;
BOOL keyIDHasElements = [[[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil] count] > 0;
if (keyIDHasElements)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
});
else {
[PearlAlert showAlertWithTitle:@"New Master Password"
message:
@"Please confirm the spelling of this new master password."
viewStyle:UIAlertViewStyleSecureTextInput
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) {
[[MPAppDelegate get] updateKey:nil];
return;
}
if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) {
[PearlAlert showAlertWithTitle:@"Incorrect Master Password"
message:
@"The password you entered doesn't match with the master password you tried to use. "
@"You've probably mistyped one of them.\n\n"
@"Give it another try."
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
return;
}
[self dismissModalViewControllerAnimated:YES];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", [MPAppDelegate get].keyID];
fetchRequest.fetchLimit = 1;
BOOL keyIDHasElements = [[[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil] count] > 0;
if (keyIDHasElements)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
});
else {
[PearlAlert showAlertWithTitle:@"New Master Password"
message:
@"Please confirm the spelling of this new master password."
viewStyle:UIAlertViewStyleSecureTextInput
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) {
[[MPAppDelegate get] updateKey:nil];
return;
}
cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
});
else
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Not valid." state:MPLockscreenError];
[UIView animateWithDuration:0.5f animations:^{
self.changeMPView.alpha = 1.0f;
}];
});
}
@finally {
if (![[alert textFieldAtIndex:0].text isEqualToString:textField.text]) {
[PearlAlert showAlertWithTitle:@"Incorrect Master Password"
message:
@"The password you entered doesn't match with the master password you tried to use. "
@"You've probably mistyped one of them.\n\n"
@"Give it another try."
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
return;
}
[self dismissModalViewControllerAnimated:YES];
}
cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
} else {
[self showMessage:@"Not valid." state:MPLockscreenError];
[UIView animateWithDuration:0.5f animations:^{
self.changeMPView.alpha = 1.0f;
}];
}
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3f animations:^{
self.spinner.alpha = 0.0f;
@@ -226,7 +222,7 @@ typedef enum {
[self.spinner.layer removeAllAnimations];
}];
});
}
});
});
}