1// Copyright 2015-present 650 Industries. All rights reserved. 2 3@import UIKit; 4 5#import "EXAppDelegate.h" 6#import "EXAppViewController.h" 7#import "EXHomeAppManager.h" 8#import "EXKernel.h" 9#import "EXAppLoader.h" 10#import "EXKernelAppRecord.h" 11#import "EXKernelAppRegistry.h" 12#import "EXKernelLinkingManager.h" 13#import "EXKernelServiceRegistry.h" 14#import "EXRootViewController.h" 15#import "EXDevMenuManager.h" 16 17NSString * const kEXHomeDisableNuxDefaultsKey = @"EXKernelDisableNuxDefaultsKey"; 18NSString * const kEXHomeIsNuxFinishedDefaultsKey = @"EXHomeIsNuxFinishedDefaultsKey"; 19 20NS_ASSUME_NONNULL_BEGIN 21 22@interface EXRootViewController () <EXAppBrowserController> 23 24@property (nonatomic, assign) BOOL isAnimatingAppTransition; 25 26@end 27 28@implementation EXRootViewController 29 30- (instancetype)init 31{ 32 if (self = [super init]) { 33 [EXKernel sharedInstance].browserController = self; 34 [self _maybeResetNuxState]; 35 } 36 return self; 37} 38 39#pragma mark - EXViewController 40 41- (void)createRootAppAndMakeVisible 42{ 43 EXHomeAppManager *homeAppManager = [[EXHomeAppManager alloc] init]; 44 EXAppLoader *homeAppLoader = [[EXAppLoader alloc] initWithLocalManifest:[EXHomeAppManager bundledHomeManifest]]; 45 EXKernelAppRecord *homeAppRecord = [[EXKernelAppRecord alloc] initWithAppLoader:homeAppLoader appManager:homeAppManager]; 46 [[EXKernel sharedInstance].appRegistry registerHomeAppRecord:homeAppRecord]; 47 [self moveAppToVisible:homeAppRecord]; 48} 49 50#pragma mark - EXAppBrowserController 51 52- (void)moveAppToVisible:(EXKernelAppRecord *)appRecord 53{ 54 [self _foregroundAppRecord:appRecord]; 55 56 // When foregrounding the app record we want to add it to the history to handle the edge case 57 // where a user opened a project, then went to home and cleared history, then went back to a 58 // the already open project. 59 [self addHistoryItemWithUrl:appRecord.appLoader.manifestUrl manifest:appRecord.appLoader.manifest]; 60 61} 62 63- (void)showQRReader 64{ 65 [self moveHomeToVisible]; 66 [[self _getHomeAppManager] showQRReader]; 67} 68 69- (void)moveHomeToVisible 70{ 71 [[EXDevMenuManager sharedInstance] close]; 72 [self moveAppToVisible:[EXKernel sharedInstance].appRegistry.homeAppRecord]; 73} 74 75// this is different from Util.reload() 76// because it can work even on an errored app record (e.g. with no manifest, or with no running bridge). 77- (void)reloadVisibleApp 78{ 79 [[EXDevMenuManager sharedInstance] close]; 80 81 EXKernelAppRecord *visibleApp = [EXKernel sharedInstance].visibleApp; 82 [[EXKernel sharedInstance] logAnalyticsEvent:@"RELOAD_EXPERIENCE" forAppRecord:visibleApp]; 83 NSURL *urlToRefresh = visibleApp.appLoader.manifestUrl; 84 85 // Unregister visible app record so all modules get destroyed. 86 [[[EXKernel sharedInstance] appRegistry] unregisterAppWithRecord:visibleApp]; 87 88 // Create new app record. 89 [[EXKernel sharedInstance] createNewAppWithUrl:urlToRefresh initialProps:nil]; 90} 91 92- (void)addHistoryItemWithUrl:(NSURL *)manifestUrl manifest:(NSDictionary *)manifest 93{ 94 [[self _getHomeAppManager] addHistoryItemWithUrl:manifestUrl manifest:manifest]; 95} 96 97- (void)getHistoryUrlForExperienceId:(NSString *)experienceId completion:(void (^)(NSString *))completion 98{ 99 return [[self _getHomeAppManager] getHistoryUrlForExperienceId:experienceId completion:completion]; 100} 101 102- (void)setIsNuxFinished:(BOOL)isFinished 103{ 104 [[NSUserDefaults standardUserDefaults] setBool:isFinished forKey:kEXHomeIsNuxFinishedDefaultsKey]; 105 [[NSUserDefaults standardUserDefaults] synchronize]; 106} 107 108- (BOOL)isNuxFinished 109{ 110 return [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeIsNuxFinishedDefaultsKey]; 111} 112 113- (void)appDidFinishLoadingSuccessfully:(EXKernelAppRecord *)appRecord 114{ 115 // show nux if needed 116 if (!self.isNuxFinished 117 && appRecord == [EXKernel sharedInstance].visibleApp 118 && appRecord != [EXKernel sharedInstance].appRegistry.homeAppRecord) { 119 [[EXDevMenuManager sharedInstance] open]; 120 } 121} 122 123#pragma mark - internal 124 125- (void)_foregroundAppRecord:(EXKernelAppRecord *)appRecord 126{ 127 if (_isAnimatingAppTransition) { 128 return; 129 } 130 EXAppViewController *viewControllerToShow = appRecord.viewController; 131 EXAppViewController *viewControllerToHide; 132 if (viewControllerToShow != self.contentViewController) { 133 _isAnimatingAppTransition = YES; 134 if (self.contentViewController) { 135 viewControllerToHide = (EXAppViewController *)self.contentViewController; 136 } 137 if (viewControllerToShow) { 138 [viewControllerToShow willMoveToParentViewController:self]; 139 [self.view addSubview:viewControllerToShow.view]; 140 [viewControllerToShow foregroundControllers]; 141 } 142 143 __weak typeof(self) weakSelf = self; 144 void (^transitionFinished)(void) = ^{ 145 __strong typeof(weakSelf) strongSelf = weakSelf; 146 if (strongSelf) { 147 if (viewControllerToHide) { 148 // backgrounds and then dismisses all modals that are presented by the app 149 [viewControllerToHide backgroundControllers]; 150 [viewControllerToHide dismissViewControllerAnimated:NO completion:nil]; 151 [viewControllerToHide willMoveToParentViewController:nil]; 152 [viewControllerToHide.view removeFromSuperview]; 153 [viewControllerToHide didMoveToParentViewController:nil]; 154 } 155 if (viewControllerToShow) { 156 [viewControllerToShow didMoveToParentViewController:strongSelf]; 157 strongSelf.contentViewController = viewControllerToShow; 158 } 159 [strongSelf.view setNeedsLayout]; 160 strongSelf.isAnimatingAppTransition = NO; 161 if (strongSelf.delegate) { 162 [strongSelf.delegate viewController:strongSelf didNavigateAppToVisible:appRecord]; 163 } 164 } 165 }; 166 167 BOOL animated = (viewControllerToHide && viewControllerToShow); 168 if (animated) { 169 if (viewControllerToHide.contentView) { 170 viewControllerToHide.contentView.transform = CGAffineTransformIdentity; 171 viewControllerToHide.contentView.alpha = 1.0f; 172 } 173 if (viewControllerToShow.contentView) { 174 viewControllerToShow.contentView.transform = CGAffineTransformMakeScale(1.1f, 1.1f); 175 viewControllerToShow.contentView.alpha = 0; 176 } 177 [UIView animateWithDuration:0.3f animations:^{ 178 if (viewControllerToHide.contentView) { 179 viewControllerToHide.contentView.transform = CGAffineTransformMakeScale(0.95f, 0.95f); 180 viewControllerToHide.contentView.alpha = 0.5f; 181 } 182 if (viewControllerToShow.contentView) { 183 viewControllerToShow.contentView.transform = CGAffineTransformIdentity; 184 viewControllerToShow.contentView.alpha = 1.0f; 185 } 186 } completion:^(BOOL finished) { 187 transitionFinished(); 188 }]; 189 } else { 190 transitionFinished(); 191 } 192 } 193} 194 195- (EXHomeAppManager *)_getHomeAppManager 196{ 197 return (EXHomeAppManager *)[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager; 198} 199 200- (void)_maybeResetNuxState 201{ 202 // used by appetize: optionally disable nux 203 BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeDisableNuxDefaultsKey]; 204 if (disableNuxDefaultsValue) { 205 [self setIsNuxFinished:YES]; 206 [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXHomeDisableNuxDefaultsKey]; 207 } 208} 209 210@end 211 212NS_ASSUME_NONNULL_END 213