// // OSKSessionController.m // Overshare // // Created by Jared Sinclair on 10/11/13. // Copyright (c) 2013 Overshare Kit. All rights reserved. // @import Accounts; #import "OSKSessionController.h" #import "OSKActivitiesManager.h" #import "OSKActivity.h" #import "OSKActivity_SystemAccounts.h" #import "OSKActivity_GenericAuthentication.h" #import "OSKActivity_ManagedAccounts.h" #import "OSKActivitySheetViewController.h" #import "OSKAppDotNetActivity.h" #import "OSKAuthenticationViewController.h" #import "OSKLogger.h" #import "OSKPublishingViewController.h" #import "OSKPurchasingViewController.h" #import "OSKPresentationManager.h" #import "OSKManagedAccount.h" #import "OSKManagedAccountStore.h" #import "OSKSystemAccountStore.h" #import "OSKAlertView.h" #import "OSKSession.h" #import "OSKURLSchemeActivity.h" static NSString * const OSKAccountAuthenticationFailure_NoAccounts = @"OSKAccountAuthenticationFailure_NoAccounts"; static NSString * const OSKAccountAuthenticationFailure_AccessNotGranted = @"OSKAccountAuthenticationFailure_AccessNotGranted"; @interface OSKSessionController () < OSKPurchasingViewControllerDelegate, OSKAuthenticationViewControllerDelegate, OSKPublishingViewControllerDelegate > @property (strong, nonatomic, readwrite) OSKSession *session; @property ( weak, nonatomic, readwrite) id delegate; @property (strong, nonatomic, readwrite) OSKActivity *activity; @end @implementation OSKSessionController - (instancetype)initWithActivity:(OSKActivity *)activity session:(OSKSession *)session delegate:(id)delegate { self = [super init]; if (self) { _session = session; _delegate = delegate; _activity = activity; } return self; } #pragma mark - Tapped Activity Flow - (void)start { [self handlePurchasingStepForActivity:self.activity]; } - (void)handlePurchasingStepForActivity:(OSKActivity *)activity { if ([activity requiresPurchase]) { if ([activity isAlreadyPurchased]) { [self handleAuthenticationStepForActivity:activity]; } else { [self showPurchasingViewControllerForActivity:activity]; } } else { [self handleAuthenticationStepForActivity:activity]; } } - (void)handleAuthenticationStepForActivity:(OSKActivity *)activity { OSKAuthenticationMethod authenticationMethod = [activity.class authenticationMethod]; if (authenticationMethod == OSKAuthenticationMethod_ManagedAccounts) { [self handleManagedAccountAuthenticationStepForActivity:activity]; } else if (authenticationMethod == OSKAuthenticationMethod_SystemAccounts) { [self handleSystemAccountAuthenticationStepForActivity:activity]; } else if (authenticationMethod == OSKAuthenticationMethod_Generic) { [self handleGenericAuthenticationStepForActivity:activity]; } else { [self handlePublishingStepForActivity:activity]; } } - (void)handleSystemAccountAuthenticationStepForActivity:(OSKActivity *)activity { NSAssert([activity respondsToSelector:@selector(activeSystemAccount)], @"OSKActivity subclasses using the system account authentication method must conform to the OSKActivity_SystemAccounts protocol."); OSKActivity *theActivity = (OSKActivity *)activity; if ([[[theActivity class] activityType] isEqualToString:OSKActivityType_iOS_Facebook]) { // Facebook accounts must request for read permissions before asking for write permissions // (which is a maddening API decision). Thus, we must use a separate flow for Facebook authentication. [self handleFacebookAccountAuthenticationStepForActivity:theActivity]; } else { OSKSystemAccountStore *accountStore = [OSKSystemAccountStore sharedInstance]; NSString *systemAccountTypeIdentifier = [theActivity.class systemAccountTypeIdentifier]; NSArray *existingAccounts = [accountStore accountsForAccountTypeIdentifier:systemAccountTypeIdentifier]; NSString *lastUsedAccountID = [accountStore lastUsedAccountIdentifierForType:systemAccountTypeIdentifier]; if (existingAccounts.count > 0) { ACAccount *account = nil; for (ACAccount *anAccount in existingAccounts) { if ([anAccount.identifier isEqualToString:lastUsedAccountID]) { account = anAccount; break; } } if (account == nil) { account = [existingAccounts firstObject]; } [theActivity setActiveSystemAccount:account]; [self handlePublishingStepForActivity:activity]; } else { __weak OSKSessionController *weakSelf = self; NSString *systemAccountTypeIdentifier = [theActivity.class systemAccountTypeIdentifier]; [accountStore requestAccessToAccountsWithAccountTypeIdentifier:systemAccountTypeIdentifier options:nil completion:^(BOOL successful, NSError *error) { if (successful) { NSArray *systemAccounts = [accountStore accountsForAccountTypeIdentifier:systemAccountTypeIdentifier]; if (systemAccounts.count > 0) { ACAccount *account = [systemAccounts firstObject]; [theActivity setActiveSystemAccount:account]; [weakSelf handlePublishingStepForActivity:activity]; } else { [weakSelf handleSystemAccountAccessFailure:OSKAccountAuthenticationFailure_NoAccounts withActivity:activity]; [weakSelf cancel]; } } else { [weakSelf handleSystemAccountAccessFailure:OSKAccountAuthenticationFailure_AccessNotGranted withActivity:activity]; [weakSelf cancel]; } }]; } } } - (void)handleFacebookAccountAuthenticationStepForActivity:(OSKActivity *)activity { NSAssert([[[activity class] activityType] isEqualToString:OSKActivityType_iOS_Facebook], @"Attempting to authenticate a non-Facebook activity via the Facebook flow."); OSKSystemAccountStore *accountStore = [OSKSystemAccountStore sharedInstance]; NSDictionary *readOptions = [activity.class readAccessRequestOptions]; NSDictionary *writeOptions = [activity.class writeAccessRequestOptions]; __weak OSKSessionController *weakSelf = self; NSString *systemAccountTypeIdentifier = [activity.class systemAccountTypeIdentifier]; [accountStore requestAccessToAccountsWithAccountTypeIdentifier:systemAccountTypeIdentifier options:readOptions completion:^(BOOL successful, NSError *error) { if (successful == NO) { [weakSelf handleSystemAccountAccessFailure:OSKAccountAuthenticationFailure_AccessNotGranted withActivity:activity]; [weakSelf cancel]; } else { [accountStore requestAccessToAccountsWithAccountTypeIdentifier:systemAccountTypeIdentifier options:writeOptions completion:^(BOOL successful, NSError *error) { if (successful == NO) { [weakSelf handleSystemAccountAccessFailure:OSKAccountAuthenticationFailure_AccessNotGranted withActivity:activity]; [weakSelf cancel]; } else { NSArray *systemAccounts = [accountStore accountsForAccountTypeIdentifier:systemAccountTypeIdentifier]; if (systemAccounts.count > 0) { ACAccount *account = nil; NSString *lastUsedAccountID = [accountStore lastUsedAccountIdentifierForType:systemAccountTypeIdentifier]; for (ACAccount *anAccount in systemAccounts) { if ([anAccount.identifier isEqualToString:lastUsedAccountID]) { account = anAccount; break; } } if (account == nil) { account = [systemAccounts firstObject]; } [activity setActiveSystemAccount:account]; [weakSelf handlePublishingStepForActivity:activity]; } else { [weakSelf handleSystemAccountAccessFailure:OSKAccountAuthenticationFailure_NoAccounts withActivity:activity]; } } }]; } }]; } - (void)handleSystemAccountAccessFailure:(NSString *)failureType withActivity:(OSKActivity *)activity { void (^defaultHandler)() = nil; if (failureType == OSKAccountAuthenticationFailure_NoAccounts) { OSKLog(@"User has no existing system accounts with account type identifier: %@", [activity.class systemAccountTypeIdentifier]); defaultHandler = ^void() { [self showAlertForNoSystemAccounts]; }; } else if (failureType == OSKAccountAuthenticationFailure_AccessNotGranted) { OSKLog(@"Access request failed for system accounts with account type identifier: %@", [activity.class systemAccountTypeIdentifier]); defaultHandler = ^void() { [self showAlertForSystemAccountAccessNotGranted]; }; } if ([self.delegate respondsToSelector:@selector(sessionController:failedSystemAccountAccessForActivity:withDefaultHandler:)]) { [self.delegate sessionController:self failedSystemAccountAccessForActivity:activity withDefaultHandler:defaultHandler]; } else if (defaultHandler) { defaultHandler(); } } - (void)showAlertForNoSystemAccounts { OSKPresentationManager *presentationManger = [OSKPresentationManager sharedInstance]; NSString *title = [presentationManger localizedText_NoAccountsFound]; NSString *message = [presentationManger localizedText_YouCanSignIntoYourAccountsViaTheSettingsApp]; OSKAlertViewButtonItem *okayButton = [OSKAlertView okayItem]; OSKAlertView *alert = [[OSKAlertView alloc] initWithTitle:title message:message cancelButtonItem:okayButton otherButtonItems:nil]; [alert show]; } - (void)showAlertForSystemAccountAccessNotGranted { OSKPresentationManager *presentationManger = [OSKPresentationManager sharedInstance]; NSString *title = [presentationManger localizedText_AccessNotGrantedForSystemAccounts_Title]; NSString *message = [presentationManger localizedText_AccessNotGrantedForSystemAccounts_Message]; OSKAlertViewButtonItem *okayButton = [OSKAlertView okayItem]; OSKAlertView *alert = [[OSKAlertView alloc] initWithTitle:title message:message cancelButtonItem:okayButton otherButtonItems:nil]; [alert show]; } - (void)finishUpAuthenticationWithFirstNewManagedAccount:(OSKManagedAccount *)account activity:(OSKActivity *)activity { OSKManagedAccountStore *accountStore = [OSKManagedAccountStore sharedInstance]; [accountStore addAccount:account forActivityType:[activity.class activityType]]; [activity setActiveManagedAccount:account]; [self handlePublishingStepForActivity:activity]; } - (void)handleManagedAccountAuthenticationStepForActivity:(OSKActivity *)activity { NSAssert([activity respondsToSelector:@selector(activeManagedAccount)], @"OSKActivity subclasses using the managed account authentication method must conform to the OSKActivity_ManagedAccounts protocol."); OSKActivity *theActivity = (OSKActivity *)activity; OSKManagedAccountStore *accountStore = [OSKManagedAccountStore sharedInstance]; NSArray *existingAccounts = [accountStore accountsForActivityType:[theActivity.class activityType]]; if (existingAccounts.count > 0) { OSKManagedAccount *activeAccount = [accountStore activeAccountForActivityType:[activity.class activityType]]; [theActivity setActiveManagedAccount:activeAccount]; [self handlePublishingStepForActivity:theActivity]; } else if ([theActivity.class authenticationViewControllerType] != OSKManagedAccountAuthenticationViewControllerType_None) { [self showAuthenticationViewControllerForActivity:theActivity]; } else { __weak OSKSessionController *weakSelf = self; [theActivity authenticateNewAccountWithoutViewController:^(OSKManagedAccount *account, NSError *error) { if (account) { [weakSelf finishUpAuthenticationWithFirstNewManagedAccount:account activity:theActivity]; } else { OSKLog(@"Failed to add account for activity: %@ error: %@", activity, error); [weakSelf cancel]; } }]; } } - (void)handleGenericAuthenticationStepForActivity:(OSKActivity *)activity { NSAssert([activity respondsToSelector:@selector(authenticate:)], @"OSKActivity subclasses using the generic authentication method must conform to the OSKActivity_GenericAuthentication protocol."); OSKActivity *theActivity = (OSKActivity *)activity; if ([theActivity isAuthenticated]) { [self handlePublishingStepForActivity:theActivity]; } else { __weak OSKSessionController *weakSelf = self; [theActivity authenticate:^(BOOL successful, NSError *error) { if (successful) { [weakSelf handlePublishingStepForActivity:theActivity]; } else { OSKLog(@"Activity failed to authenticate: %@ error: %@", theActivity, error); [weakSelf cancel]; } }]; } } - (void)handlePublishingStepForActivity:(OSKActivity *)activity { OSKPublishingMethod method = [activity.class publishingMethod]; if (method == OSKPublishingMethod_None || method == OSKPublishingMethod_URLScheme) { [self dismissViewControllers]; [self handlePerformStepForActivity:activity]; } else { [self showPublishingViewControllerForActivity:activity]; } } - (void)handlePerformStepForActivity:(OSKActivity *)activity { [self.delegate sessionControllerDidBeginPerformingActivity:self hasDismissedAllViewControllers:YES]; // Always YES for now... if ([activity.class publishingMethod] == OSKPublishingMethod_URLScheme) { [self prepareURLSchemeActivityToPerform:activity]; } __weak OSKSessionController *weakSelf = self; [activity performActivity:^(OSKActivity *theActivity, BOOL successful, NSError *error) { [weakSelf.delegate sessionControllerDidFinish:weakSelf successful:successful error:error]; }]; } - (void)prepareURLSchemeActivityToPerform:(OSKActivity *)activity { if ([activity conformsToProtocol:@protocol(OSKURLSchemeActivity)]) { OSKActivity *urlSchemeActivity = (OSKActivity *)activity; if ([urlSchemeActivity targetApplicationSupportsXCallbackURL]) { if ([urlSchemeActivity respondsToSelector:@selector(prepareToPerformActionUsingXCallbackURLInfo:)]) { id xCallbackInfo = [OSKActivitiesManager sharedInstance].xCallbackURLDelegate; [urlSchemeActivity prepareToPerformActionUsingXCallbackURLInfo:xCallbackInfo]; } } } } #pragma mark - Required for Subclasses - (void)presentViewControllerAppropriately:(UIViewController *)viewController setAsNewRoot:(BOOL)isNewRoot { NSAssert(NO, @"Subclasses must override presentViewControllerAppropriately:setAsNewRoot: without calling super"); } - (void)presentSystemViewControllerAppropriately:(UIViewController *)systemViewController { NSAssert(NO, @"Subclasses must override presentViewControllerAppropriately:setAsNewRoot: without calling super"); } - (void)dismissViewControllers { NSAssert(NO, @"Subclasses must override dismissAllViewControllers without calling super"); } #pragma mark - Setting Up Purchasing View Controllers - (void)showPurchasingViewControllerForActivity:(OSKActivity *)activity { UIViewController *viewController = nil; viewController = [[OSKPresentationManager sharedInstance] purchasingViewControllerForActivity:activity]; [viewController preparePurchasingViewForActivity:activity delegate:self]; [self presentViewControllerAppropriately:viewController setAsNewRoot:YES]; } #pragma mark - Setting Up Authentication View Controllers - (void)showAuthenticationViewControllerForActivity:(OSKActivity *)activity { UIViewController *viewController = nil; viewController = [[OSKPresentationManager sharedInstance] authenticationViewControllerForActivity:activity]; [viewController prepareAuthenticationViewForActivity:(OSKActivity *)activity delegate:self]; [self presentViewControllerAppropriately:viewController setAsNewRoot:YES]; } #pragma mark - Setting Up Publishing View Controllers - (void)showPublishingViewControllerForActivity:(OSKActivity *)activity { UIViewController *viewController = nil; viewController = [[OSKPresentationManager sharedInstance] publishingViewControllerForActivity:activity]; [viewController preparePublishingViewForActivity:activity delegate:self]; if ([activity.class publishingMethod] == OSKPublishingMethod_ViewController_System) { [self presentSystemViewControllerAppropriately:viewController]; } else { [self presentViewControllerAppropriately:viewController setAsNewRoot:YES]; } } #pragma mark - Purchasing View Controller Delegate - (void)purchasingViewController:(UIViewController *)viewController didPurchaseActivityTypes:(NSArray *)activityTypes withActivity:(OSKActivity *)activity { OSKActivitiesManager *manager = [OSKActivitiesManager sharedInstance]; [manager markActivityTypes:activityTypes asAlreadyPurchased:YES]; [self handleAuthenticationStepForActivity:activity]; } - (void)purchasingViewControllerDidCancel:(UIViewController *)viewController withActivity:(OSKActivity *)activity { [self cancel]; } #pragma mark - Authentication View Controller Delegate - (void)authenticationViewController:(UIViewController *)viewController didAuthenticateNewAccount:(OSKManagedAccount *)account withActivity:(OSKActivity *)activity { [self finishUpAuthenticationWithFirstNewManagedAccount:account activity:(OSKActivity *)activity]; } - (void)authenticationViewControllerDidCancel:(UIViewController *)viewController withActivity:(OSKActivity *)activity { [self cancel]; } #pragma mark - Publishing View Controller Delegate - (void)publishingViewController:(UIViewController *)viewController didTapPublishActivity:(OSKActivity *)activity { if ([activity isReadyToPerform]) { [self dismissViewControllers]; [self handlePerformStepForActivity:activity]; } else { OSKLog(@"Publishing failed, not yet ready to perform: %@", activity); } } - (void)publishingViewControllerDidCancel:(UIViewController *)viewController withActivity:(OSKActivity *)activity { [self cancel]; } #pragma mark - Convenience - (void)cancel { [self dismissViewControllers]; [self.delegate sessionControllerDidCancel:self]; } @end