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