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 "EXAppLoader.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 EXAppLoader *homeAppLoader = [[EXAppLoader 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 [[EXKernel sharedInstance] logAnalyticsEvent:@"RELOAD_EXPERIENCE" forAppRecord:visibleApp]; 117 NSURL *urlToRefresh = visibleApp.appLoader.manifestUrl; 118 119 // Unregister visible app record so all modules get destroyed. 120 [[[EXKernel sharedInstance] appRegistry] unregisterAppWithRecord:visibleApp]; 121 122 // Create new app record. 123 [[EXKernel sharedInstance] createNewAppWithUrl:urlToRefresh initialProps:nil]; 124} 125 126- (void)addHistoryItemWithUrl:(NSURL *)manifestUrl manifest:(EXManifestsManifest *)manifest 127{ 128 [[self _getHomeAppManager] addHistoryItemWithUrl:manifestUrl manifest:manifest]; 129} 130 131- (void)getHistoryUrlForScopeKey:(NSString *)scopeKey completion:(void (^)(NSString *))completion 132{ 133 return [[self _getHomeAppManager] getHistoryUrlForScopeKey:scopeKey completion:completion]; 134} 135 136- (void)setIsNuxFinished:(BOOL)isFinished 137{ 138 [[NSUserDefaults standardUserDefaults] setBool:isFinished forKey:kEXHomeIsNuxFinishedDefaultsKey]; 139 [[NSUserDefaults standardUserDefaults] synchronize]; 140} 141 142- (BOOL)isNuxFinished 143{ 144 return [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeIsNuxFinishedDefaultsKey]; 145} 146 147- (void)appDidFinishLoadingSuccessfully:(EXKernelAppRecord *)appRecord 148{ 149 // show nux if needed 150 if (!self.isNuxFinished 151 && appRecord == [EXKernel sharedInstance].visibleApp 152 && appRecord != [EXKernel sharedInstance].appRegistry.homeAppRecord) { 153 [[EXDevMenuManager sharedInstance] open]; 154 } 155} 156 157#pragma mark - internal 158 159- (void)_foregroundAppRecord:(EXKernelAppRecord *)appRecord 160{ 161 // Some transition is in progress 162 if (_isAnimatingAppTransition) { 163 return; 164 } 165 166 EXAppViewController *viewControllerToShow = appRecord.viewController; 167 168 // Tried to foregroung the very same view controller 169 if (viewControllerToShow == self.contentViewController) { 170 return; 171 } 172 173 _isAnimatingAppTransition = YES; 174 175 EXAppViewController *viewControllerToHide = (EXAppViewController *)self.contentViewController; 176 177 if (viewControllerToShow) { 178 [self.view addSubview:viewControllerToShow.view]; 179 [self addChildViewController:viewControllerToShow]; 180 } 181 182 EX_WEAKIFY(self) 183 void (^finalizeTransition)(void) = ^{ 184 EX_ENSURE_STRONGIFY(self) 185 if (viewControllerToHide) { 186 // backgrounds and then dismisses all modals that are presented by the app 187 [viewControllerToHide backgroundControllers]; 188 [viewControllerToHide dismissViewControllerAnimated:NO completion:nil]; 189 [viewControllerToHide willMoveToParentViewController:nil]; 190 [viewControllerToHide removeFromParentViewController]; 191 [viewControllerToHide.view removeFromSuperview]; 192 } 193 194 if (viewControllerToShow) { 195 [viewControllerToShow didMoveToParentViewController:self]; 196 self.contentViewController = viewControllerToShow; 197 } 198 199 [self.view setNeedsLayout]; 200 self.isAnimatingAppTransition = NO; 201 if (self.delegate) { 202 [self.delegate viewController:self didNavigateAppToVisible:appRecord]; 203 } 204 }; 205 206 BOOL animated = (viewControllerToHide && viewControllerToShow); 207 if (!animated) { 208 return finalizeTransition(); 209 } 210 211 if (viewControllerToHide.contentView) { 212 viewControllerToHide.contentView.transform = CGAffineTransformIdentity; 213 viewControllerToHide.contentView.alpha = 1.0f; 214 } 215 if (viewControllerToShow.contentView) { 216 viewControllerToShow.contentView.transform = CGAffineTransformMakeScale(1.1f, 1.1f); 217 viewControllerToShow.contentView.alpha = 0; 218 } 219 220 [UIView animateWithDuration:0.3f animations:^{ 221 if (viewControllerToHide.contentView) { 222 viewControllerToHide.contentView.transform = CGAffineTransformMakeScale(0.95f, 0.95f); 223 viewControllerToHide.contentView.alpha = 0.5f; 224 } 225 if (viewControllerToShow.contentView) { 226 viewControllerToShow.contentView.transform = CGAffineTransformIdentity; 227 viewControllerToShow.contentView.alpha = 1.0f; 228 } 229 } completion:^(BOOL finished) { 230 finalizeTransition(); 231 }]; 232} 233 234- (EXHomeAppManager *)_getHomeAppManager 235{ 236 return (EXHomeAppManager *)[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager; 237} 238 239- (void)_maybeResetNuxState 240{ 241 // used by appetize: optionally disable nux 242 BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeDisableNuxDefaultsKey]; 243 if (disableNuxDefaultsValue) { 244 [self setIsNuxFinished:YES]; 245 [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXHomeDisableNuxDefaultsKey]; 246 } 247} 248 249@end 250 251NS_ASSUME_NONNULL_END 252