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