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:
@@ -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 {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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..
|
||||
|
@@ -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];
|
||||
}];
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user