2
0
MasterPassword/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m
2013-11-07 22:03:34 -05:00

893 lines
44 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MPiOSAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "IASKSettingsReader.h"
@interface MPiOSAppDelegate()
@property(nonatomic, strong) PearlAlert *handleCloudContentAlert;
@property(nonatomic, strong) PearlAlert *fixCloudContentAlert;
@property(nonatomic, strong) PearlOverlay *storeLoading;
@end
@implementation MPiOSAppDelegate
+ (void)initialize {
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
#ifdef DEBUG
[PearlLogger get].printLevel = PearlLogLevelDebug;
#else
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
#endif
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
@try {
[[NSBundle mainBundle] mutableInfoDictionary][@"CFBundleDisplayName"] = @"Master Password";
[[NSBundle mainBundle] mutableLocalizedInfoDictionary][@"CFBundleDisplayName"] = @"Master Password";
#ifdef TESTFLIGHT_SDK_VERSION
NSString *testFlightToken = [self testFlightToken];
if ([testFlightToken length]) {
inf(@"Initializing TestFlight");
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
[TestFlight addCustomEnvironmentInformation:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
[TestFlight setOptions:@{
@"logToConsole" : @NO,
@"logToSTDERR" : @NO
}];
[TestFlight takeOff:testFlightToken];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelDebug;
if (message.level >= level)
TFLog( @"%@", [message messageDescription] );
return YES;
}];
TFLog( @"TestFlight (%@) initialized for: %@ v%@.", //
TESTFLIGHT_SDK_VERSION, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion );
}
#endif
NSString *googlePlusClientID = [self googlePlusClientID];
if ([googlePlusClientID length]) {
inf(@"Initializing Google+");
[[GPPSignIn sharedInstance] setClientID:googlePlusClientID];
}
#ifdef CRASHLYTICS
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
if ([crashlyticsAPIKey length]) {
inf(@"Initializing Crashlytics");
#if defined (DEBUG) || defined (ADHOC)
[Crashlytics sharedInstance].debugMode = YES;
#endif
[Crashlytics setUserIdentifier:[PearlKeyChain deviceIdentifier]];
[Crashlytics setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
[Crashlytics setUserName:@"Anonymous"];
[Crashlytics setObjectValue:@"Anonymous" forKey:@"username"];
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelInfo;
if ([[MPiOSConfig get].sendInfo boolValue])
level = PearlLogLevelDebug;
if (message.level >= level)
CLSLog( @"%@", [message messageDescription] );
return YES;
}];
CLSLog( @"Crashlytics (%@) initialized for: %@ v%@.", //
[Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion );
}
#endif
#ifdef LOCALYTICS
NSString *localyticsKey = [self localyticsKey];
if ([localyticsKey length]) {
inf(@"Initializing Localytics");
[[LocalyticsSession sharedLocalyticsSession] LocalyticsSession:localyticsKey];
[[LocalyticsSession sharedLocalyticsSession] open];
[LocalyticsSession sharedLocalyticsSession].enableHTTPS = YES;
[[LocalyticsSession sharedLocalyticsSession] setCustomerId:[PearlKeyChain deviceIdentifier]];
[[LocalyticsSession sharedLocalyticsSession] setCustomerName:@"Anonymous"];
[[LocalyticsSession sharedLocalyticsSession] upload];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelWarn)
MPCheckpoint( @"Problem", @{
@"level" : @(PearlLogLevelStr( message.level )),
@"message" : NilToNSNull(message.message)
} );
return YES;
}];
}
#endif
}
@catch (id exception) {
err(@"During Analytics Setup: %@", exception);
}
@try {
if (floor( NSFoundationVersionNumber ) <= NSFoundationVersionNumber_iOS_6_1) {
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];
[[UINavigationBar appearance] setTitleTextAttributes:
@{
UITextAttributeTextColor : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f],
UITextAttributeTextShadowColor : [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f],
UITextAttributeTextShadowOffset : [NSValue valueWithUIOffset:UIOffsetMake( 0, -1 )],
UITextAttributeFont : [UIFont fontWithName:@"Exo-Bold" size:20.0f]
}];
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake( 0, 5, 0, 5 )];
UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake( 0, 13, 0, 5 )];
[[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance]
setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance]
setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setTitleTextAttributes:
@{
UITextAttributeTextColor : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f],
UITextAttributeTextShadowColor : [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f],
UITextAttributeTextShadowOffset : [NSValue valueWithUIOffset:UIOffsetMake( 0, 1 )]//,
// Causes a bug in iOS where image views get oddly stretched... or something.
//UITextAttributeFont: [UIFont fontWithName:@"HelveticaNeue" size:13.0f]
}
forState:UIControlStateNormal];
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"]
resizableImageWithCapInsets:UIEdgeInsetsMake( 25, 5, 5, 5 )];
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
// UIImage *minImage = [[UIImage imageNamed:@"slider-minimum"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
// UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
// UIImage *thumbImage = [UIImage imageNamed:@"slider-handle"];
//
// [[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
// [[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
// [[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
//
// UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
// UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
// UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns"];
// UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel"];
// UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns"];
//
// [[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
// [[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
//
// [[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
// [[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
// [[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
}
}
@catch (id exception) {
err(@"During Theme Setup: %@", exception);
}
@try {
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:nil usingBlock:
^(NSNotification *note) {
[self checkConfig];
}];
[[NSNotificationCenter defaultCenter]
addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:^(NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
object:note userInfo:nil];
}];
#ifdef ADHOC
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
@"Thank you for taking the time to test Master Password.\n\n"
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
@"Contact me directly at:\n"
@"lhunath@lyndir.com\n"
@"Or report detailed issues at:\n"
@"https://youtrack.lyndir.com\n"
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#endif
}
@catch (id exception) {
err(@"During Config Test: %@", exception);
}
@try {
[super application:application didFinishLaunchingWithOptions:launchOptions];
}
@catch (id exception) {
err(@"During Pearl Application Launch: %@", exception);
}
@try {
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
dispatch_async( dispatch_get_main_queue(), ^{
if ([[MPiOSConfig get].showSetup boolValue])
[[MPiOSAppDelegate get] showSetup];
} );
MPCheckpoint( MPCheckpointStarted, @{
@"simulator" : PearlStringB( [PearlDeviceUtils isSimulator] ),
@"encrypted" : PearlStringB( [PearlDeviceUtils isAppEncrypted] ),
@"jailbroken" : PearlStringB( [PearlDeviceUtils isJailbroken] ),
@"platform" : [PearlDeviceUtils platform],
#ifdef APPSTORE
@"legal" : PearlStringB([PearlDeviceUtils isAppEncrypted]),
#else
@"legal" : @"YES",
#endif
} );
}
@catch (id exception) {
err(@"During Post-Startup: %@", exception);
}
return YES;
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
// No URL?
if (!url)
return NO;
// Google+
if ([[GPPSignIn sharedInstance] handleURL:url sourceApplication:sourceApplication annotation:annotation])
return YES;
// Arbitrary URL to mpsites data.
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSError *error;
NSURLResponse *response;
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error];
if (error)
err(@"While reading imported sites from %@: %@", url, error);
if (!importedSitesData)
return;
PearlOverlay *activityOverlay = [PearlOverlay showOverlayWithTitle:@"Importing"];
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
dispatch_group_t importPasswordGroup = dispatch_group_create();
dispatch_group_enter( importPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import File's Master Password"
message:PearlString( @"%@'s export was done using a different master password.\n"
@"Enter that master password to unlock the exported data.", userName )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( importPasswordGroup );
}
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
} );
dispatch_group_wait( importPasswordGroup, DISPATCH_TIME_FOREVER );
return masterPassword;
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
__block NSString *masterPassword = nil;
dispatch_group_t userPasswordGroup = dispatch_group_create();
dispatch_group_enter( userPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:PearlString( @"Master Password for\n%@", userName )
message:PearlString( @"Imports %lu sites, overwriting %lu.",
(unsigned long)importCount, (unsigned long)deleteCount )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( userPasswordGroup );
}
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
} );
dispatch_group_wait( userPasswordGroup, DISPATCH_TIME_FOREVER );
return masterPassword;
}];
switch (result) {
case MPImportResultSuccess:
case MPImportResultCancelled:
break;
case MPImportResultInternalError:
[PearlAlert showError:@"Import failed because of an internal error."];
break;
case MPImportResultMalformedInput:
[PearlAlert showError:@"The import doesn't look like a Master Password export."];
break;
case MPImportResultInvalidPassword:
[PearlAlert showError:@"Incorrect master password for the import sites."];
break;
}
[activityOverlay cancelOverlayAnimated:YES];
} );
return YES;
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
inf(@"Received memory warning.");
[super applicationDidReceiveMemoryWarning:application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
#ifdef LOCALYTICS
[[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload];
#endif
[super applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
#ifdef LOCALYTICS
[[LocalyticsSession sharedLocalyticsSession] resume];
[[LocalyticsSession sharedLocalyticsSession] upload];
#endif
[super applicationWillEnterForeground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
#ifdef LOCALYTICS
[[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload];
#endif
[super applicationWillTerminate:application];
}
- (void)applicationWillResignActive:(UIApplication *)application {
inf(@"Will deactivate");
if (![[MPiOSConfig get].rememberLogin boolValue])
[self signOutAnimated:NO];
#ifdef LOCALYTICS
[[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload];
#endif
[super applicationWillResignActive:application];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
inf(@"Re-activated");
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
object:application userInfo:nil];
#ifdef LOCALYTICS
[[LocalyticsSession sharedLocalyticsSession] resume];
[[LocalyticsSession sharedLocalyticsSession] upload];
#endif
[super applicationDidBecomeActive:application];
}
#pragma mark - Behavior
- (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
MPCheckpoint( MPCheckpointShowGuide, nil );
}
- (void)showSetup {
[self.navigationController performSegueWithIdentifier:@"MP_Setup" sender:self];
MPCheckpoint( MPCheckpointShowSetup, nil );
}
- (void)showReview {
MPCheckpoint( MPCheckpointReview, nil );
[super showReview];
}
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
if (![PearlEMail canSendMail])
[PearlAlert showAlertWithTitle:@"Feedback"
message:
@"Have a question, comment, issue or just saying thanks?\n\n"
@"We'd love to hear what you think!\n"
@"masterpassword@lyndir.com"
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
otherTitles:nil];
else if (logs)
[PearlAlert showAlertWithTitle:@"Feedback"
message:
@"Have a question, comment, issue or just saying thanks?\n\n"
@"If you're having trouble, it may help us if you can first reproduce the problem "
@"and then include log files in your message."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
[self openFeedbackWithLogs:(buttonIndex_ == [alert_ firstOtherButtonIndex]) forVC:viewController];
} cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
else
[self openFeedbackWithLogs:NO forVC:viewController];
}
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
NSString *userName = [[MPiOSAppDelegate get] activeUserForMainThread].name;
PearlLogLevel logLevel = PearlLogLevelInfo;
if (logs && ([[MPiOSConfig get].sendInfo boolValue] || [[MPiOSConfig get].traceMode boolValue]))
logLevel = PearlLogLevelDebug;
[[[PearlEMail alloc] initForEMailTo:@"Master Password Development <masterpassword@lyndir.com>"
subject:PearlString( @"Feedback for Master Password [%@]",
[[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"] )
body:PearlString( @"\n\n\n"
@"--\n"
@"%@"
@"Master Password %@, build %@",
userName? ([userName stringByAppendingString:@"\n"]): @"",
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion )
attachments:(logs
? [[PearlEMailAttachment alloc]
initWithContent:[[[PearlLogger get] formatMessagesWithLevel:logLevel]
dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain"
fileName:PearlString( @"%@-%@.log",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
[PearlKeyChain deviceIdentifier] )]
: nil), nil]
showComposerForVC:viewController];
}
- (void)export {
[PearlAlert showNotice:
@"This will export all your site names.\n\n"
@"You can open the export with a text editor to get an overview of all your sites.\n\n"
@"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes."
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
@"Would you like to make all your passwords visible in the export?\n\n"
@"A safe export will only include your stored passwords, in an encrypted manner, "
@"making the result safe from falling in the wrong hands.\n\n"
@"If all your passwords are shown and somebody else finds the export, "
@"they could gain access to all your sites!"
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0)
// Safe Export
[self exportShowPasswords:NO];
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
// Show Passwords
[self exportShowPasswords:YES];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
} otherTitles:nil];
}
- (void)exportShowPasswords:(BOOL)showPasswords {
if (![PearlEMail canSendMail]) {
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
message:
@"Your device is not yet set up for sending mail.\n"
@"Close Master Password, go into Settings and add a Mail account."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
otherTitles:nil];
return;
}
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
NSString *message;
if (showPasswords)
message = PearlString( @"Export of Master Password sites with passwords included.\n\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
else
message = PearlString( @"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
[PearlEMail sendEMailTo:nil subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain" fileName:
PearlString( @"%@ (%@).mpsites", [self activeUserForMainThread].name,
[exportDateFormatter stringFromDate:[NSDate date]] )],
nil];
}
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void (^)(void))didReset {
[PearlAlert showAlertWithTitle:@"Changing Master Password"
message:
@"If you continue, you'll be able to set a new master password.\n\n"
@"Changing your master password will cause all your generated passwords to change!\n"
@"Changing the master password back to the old one will cause your passwords to revert as well."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[moc performBlockAndWait:^{
inf(@"Unsetting master password for: %@.", user.userID);
user.keyID = nil;
[self forgetSavedKeyFor:user];
[moc saveToStore];
}];
[self signOutAnimated:YES];
if (didReset)
didReset();
MPCheckpoint( MPCheckpointChangeMP, nil );
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
#pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
object:NSStringFromSelector( configKey ) userInfo:nil];
}
- (void)checkConfig {
// iCloud enabled / disabled
BOOL iCloudEnabled = [[MPiOSConfig get].iCloudEnabled boolValue];
BOOL cloudEnabled = self.storeManager.cloudEnabled;
if (iCloudEnabled != cloudEnabled) {
if ([[MPiOSConfig get].iCloudEnabled boolValue])
[self.storeManager setCloudEnabledAndOverwriteCloudWithLocalIfConfirmed:^(void (^setConfirmationAnswer)(BOOL answer)) {
__block NSUInteger siteCount = NSNotFound;
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
NSError *error = nil;
if ((siteCount = [context countForFetchRequest:fetchRequest error:&error]) == NSNotFound) {
wrn(@"Couldn't count current sites: %@", error);
return;
}
}];
// If we currently have no sites, don't bother asking to copy them.
if (siteCount == 0) {
setConfirmationAnswer( NO );
return;
}
// The current store has sites, ask the user if he wants to copy them to the cloud
[PearlAlert showAlertWithTitle:@"Copy Sites To iCloud?"
message:@"You can either switch to your old iCloud sites "
@"or overwrite them with your current sites."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
setConfirmationAnswer( NO );
if (buttonIndex == [alert firstOtherButtonIndex])
setConfirmationAnswer( YES );
}
cancelTitle:@"Use Old" otherTitles:@"Overwrite", nil];
}];
else
[self.storeManager setCloudDisabledAndOverwriteLocalWithCloudIfConfirmed:^(void (^setConfirmationAnswer)(BOOL answer)) {
__block NSUInteger siteCount = NSNotFound;
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
NSError *error = nil;
if ((siteCount = [context countForFetchRequest:fetchRequest error:&error]) == NSNotFound) {
wrn(@"Couldn't count current sites: %@", error);
return;
}
}];
// If we currently have no sites, don't bother asking to copy them.
if (siteCount == 0) {
setConfirmationAnswer( NO );
return;
}
[PearlAlert showAlertWithTitle:@"Copy iCloud Sites?"
message:@"You can either switch to the old sites on your device "
@"or overwrite them with your current iCloud sites."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
setConfirmationAnswer( NO );
if (buttonIndex == [alert firstOtherButtonIndex])
setConfirmationAnswer( YES );
}
cancelTitle:@"Use Old" otherTitles:@"Overwrite", nil];
}];
}
// Trace mode
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
// Send info
if ([[MPiOSConfig get].sendInfo boolValue]) {
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
[PearlLogger get].printLevel = PearlLogLevelInfo;
#ifdef CRASHLYTICS
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].iCloudEnabled boolValue] forKey:@"iCloudEnabled"];
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloudDecided boolValue] forKey:@"iCloudDecided"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].sendInfo boolValue] forKey:@"sendInfo"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].helpHidden boolValue] forKey:@"helpHidden"];
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].showSetup boolValue] forKey:@"showQuickStart"];
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].firstRun boolValue] forKey:@"firstRun"];
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].launchCount intValue] forKey:@"launchCount"];
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].askForReviews boolValue] forKey:@"askForReviews"];
[[Crashlytics sharedInstance]
setIntValue:[[PearlConfig get].reviewAfterLaunches intValue] forKey:@"reviewAfterLaunches"];
[[Crashlytics sharedInstance] setObjectValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
#endif
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPConfig get].rememberLogin )
forKey:@"rememberLogin"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPiOSConfig get].iCloudEnabled )
forKey:@"iCloudEnabled"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPConfig get].iCloudDecided )
forKey:@"iCloudDecided"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPiOSConfig get].sendInfo )
forKey:@"sendInfo"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPiOSConfig get].helpHidden )
forKey:@"helpHidden"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [MPiOSConfig get].showSetup )
forKey:@"showQuickStart"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [PearlConfig get].firstRun )
forKey:@"firstRun"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [PearlConfig get].launchCount )
forKey:@"launchCount"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [PearlConfig get].askForReviews )
forKey:@"askForReviews"];
[TestFlight addCustomEnvironmentInformation:PearlStringNSB( [PearlConfig get].reviewAfterLaunches )
forKey:@"reviewAfterLaunches"];
[TestFlight addCustomEnvironmentInformation:[PearlConfig get].reviewedVersion
forKey:@"reviewedVersion"];
#endif
MPCheckpoint( MPCheckpointConfig, @{
@"rememberLogin" : @([[MPConfig get].rememberLogin boolValue]),
@"iCloudEnabled" : @([[MPiOSConfig get].iCloudEnabled boolValue]),
@"iCloudDecided" : @([[MPConfig get].iCloudDecided boolValue]),
@"sendInfo" : @([[MPiOSConfig get].sendInfo boolValue]),
@"helpHidden" : @([[MPiOSConfig get].helpHidden boolValue]),
@"showQuickStart" : @([[MPiOSConfig get].showSetup boolValue]),
@"firstRun" : @([[PearlConfig get].firstRun boolValue]),
@"launchCount" : NilToNSNull([PearlConfig get].launchCount),
@"askForReviews" : @([[PearlConfig get].askForReviews boolValue]),
@"reviewAfterLaunches" : NilToNSNull([PearlConfig get].reviewAfterLaunches),
@"reviewedVersion" : NilToNSNull([PearlConfig get].reviewedVersion)
} );
}
}
#pragma mark - UbiquityStoreManager
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
dispatch_async( dispatch_get_main_queue(), ^{
[self.handleCloudContentAlert cancelAlertAnimated:YES];
if (![self.storeLoading isVisible])
self.storeLoading = [PearlOverlay showOverlayWithTitle:@"Loading Sites"];
} );
[super ubiquityStoreManager:manager willLoadStoreIsCloud:isCloudStore];
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator
isCloud:(BOOL)isCloudStore {
[MPiOSConfig get].iCloudEnabled = @(isCloudStore);
[super ubiquityStoreManager:manager didLoadStoreForCoordinator:coordinator isCloud:isCloudStore];
dispatch_async( dispatch_get_main_queue(), ^{
[self.handleCloudContentAlert cancelAlertAnimated:YES];
[self.fixCloudContentAlert cancelAlertAnimated:YES];
[self.storeLoading cancelOverlayAnimated:YES];
} );
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager failedLoadingStoreWithCause:(UbiquityStoreErrorCause)cause context:(id)context
wasCloud:(BOOL)wasCloudStore {
dispatch_async( dispatch_get_main_queue(), ^{
[self.storeLoading cancelOverlayAnimated:YES];
} );
}
- (BOOL)ubiquityStoreManager:(UbiquityStoreManager *)manager handleCloudContentCorruptionWithHealthyStore:(BOOL)storeHealthy {
if (manager.cloudEnabled && !storeHealthy && !([self.handleCloudContentAlert.alertView isVisible] || [self.fixCloudContentAlert.alertView isVisible]))
dispatch_async( dispatch_get_main_queue(), ^{
[self.storeLoading cancelOverlayAnimated:YES];
[self showCloudContentAlert];
} );
return NO;
}
- (void)showCloudContentAlert {
__weak MPiOSAppDelegate *wSelf = self;
[self.handleCloudContentAlert cancelAlertAnimated:NO];
// TODO: Add the activity indicator back.
self.handleCloudContentAlert = [PearlAlert showAlertWithTitle:@"iCloud Sync Problem"
message:@"Waiting for your other device to autocorrect the problem..."
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex])
wSelf.fixCloudContentAlert = [PearlAlert showAlertWithTitle:@"Fix iCloud Now" message:
@"This problem can be autocorrected by opening the app on another device where you recently made changes.\n"
@"You can fix the problem from this device anyway, but recent changes from another device might get lost.\n\n"
@"You can also turn iCloud off for now."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:
^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == alert_.cancelButtonIndex)
[wSelf showCloudContentAlert];
if (buttonIndex_ == [alert_ firstOtherButtonIndex])
[wSelf.storeManager rebuildCloudContentFromCloudStoreOrLocalStore:YES];
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
[MPiOSConfig get].iCloudEnabled = @NO;
}
cancelTitle:[PearlStrings get].commonButtonBack
otherTitles:@"Fix Anyway",
@"Turn Off", nil];
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
[MPiOSConfig get].iCloudEnabled = @NO;
} cancelTitle:nil otherTitles:@"Fix Now", @"Turn Off", nil];
}
#pragma mark - Google+
- (NSDictionary *)googlePlusInfo {
static NSDictionary *googlePlusInfo = nil;
if (googlePlusInfo == nil)
googlePlusInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Google+" withExtension:@"plist"]];
return googlePlusInfo;
}
- (NSString *)googlePlusClientID {
NSString *googlePlusClientID = NSNullToNil([[self googlePlusInfo] valueForKeyPath:@"ClientID"]);
if (![googlePlusClientID length])
wrn(@"Google+ client ID not set. User won't be able to share via Google+.");
return googlePlusClientID;
}
#pragma mark - TestFlight
- (NSDictionary *)testFlightInfo {
static NSDictionary *testFlightInfo = nil;
if (testFlightInfo == nil)
testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
return testFlightInfo;
}
- (NSString *)testFlightToken {
NSString *testFlightToken = NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Application Token"]);
if (![testFlightToken length])
wrn(@"TestFlight token not set. Test Flight won't be aware of this test.");
return testFlightToken;
}
#pragma mark - Crashlytics
- (NSDictionary *)crashlyticsInfo {
static NSDictionary *crashlyticsInfo = nil;
if (crashlyticsInfo == nil)
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
return crashlyticsInfo;
}
- (NSString *)crashlyticsAPIKey {
NSString *crashlyticsAPIKey = NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
if (![crashlyticsAPIKey length])
wrn(@"Crashlytics API key not set. Crash logs won't be recorded.");
return crashlyticsAPIKey;
}
#pragma mark - Localytics
- (NSDictionary *)localyticsInfo {
static NSDictionary *localyticsInfo = nil;
if (localyticsInfo == nil)
localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
return localyticsInfo;
}
- (NSString *)localyticsKey {
#ifdef DEBUG
NSString *localyticsKey = NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
#else
NSString *localyticsKey = NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
#endif
if (![localyticsKey length])
wrn(@"Localytics key not set. Demographics won't be collected.");
return localyticsKey;
}
@end