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