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// this is different from Util.reload() 75// because it can work even on an errored app record (e.g. with no manifest, or with no running bridge). 76- (void)reloadVisibleApp 77{ 78 [[EXDevMenuManager sharedInstance] close]; 79 80 EXKernelAppRecord *visibleApp = [EXKernel sharedInstance].visibleApp; 81 [[EXKernel sharedInstance] logAnalyticsEvent:@"RELOAD_EXPERIENCE" forAppRecord:visibleApp]; 82 NSURL *urlToRefresh = visibleApp.appLoader.manifestUrl; 83 84 // Unregister visible app record so all modules get destroyed. 85 [[[EXKernel sharedInstance] appRegistry] unregisterAppWithRecord:visibleApp]; 86 87 // Create new app record. 88 [[EXKernel sharedInstance] createNewAppWithUrl:urlToRefresh initialProps:nil]; 89} 90 91- (void)addHistoryItemWithUrl:(NSURL *)manifestUrl manifest:(NSDictionary *)manifest 92{ 93 [[self _getHomeAppManager] addHistoryItemWithUrl:manifestUrl manifest:manifest]; 94} 95 96- (void)getHistoryUrlForExperienceId:(NSString *)experienceId completion:(void (^)(NSString *))completion 97{ 98 return [[self _getHomeAppManager] getHistoryUrlForExperienceId:experienceId completion:completion]; 99} 100 101- (void)setIsNuxFinished:(BOOL)isFinished 102{ 103 [[NSUserDefaults standardUserDefaults] setBool:isFinished forKey:kEXHomeIsNuxFinishedDefaultsKey]; 104 [[NSUserDefaults standardUserDefaults] synchronize]; 105} 106 107- (BOOL)isNuxFinished 108{ 109 return [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeIsNuxFinishedDefaultsKey]; 110} 111 112- (void)appDidFinishLoadingSuccessfully:(EXKernelAppRecord *)appRecord 113{ 114 // show nux if needed 115 if (!self.isNuxFinished 116 && appRecord == [EXKernel sharedInstance].visibleApp 117 && appRecord != [EXKernel sharedInstance].appRegistry.homeAppRecord) { 118 [[EXDevMenuManager sharedInstance] open]; 119 } 120} 121 122#pragma mark - internal 123 124- (void)_foregroundAppRecord:(EXKernelAppRecord *)appRecord 125{ 126 if (_isAnimatingAppTransition) { 127 return; 128 } 129 EXAppViewController *viewControllerToShow = appRecord.viewController; 130 EXAppViewController *viewControllerToHide; 131 if (viewControllerToShow != self.contentViewController) { 132 _isAnimatingAppTransition = YES; 133 if (self.contentViewController) { 134 viewControllerToHide = (EXAppViewController *)self.contentViewController; 135 } 136 if (viewControllerToShow) { 137 [viewControllerToShow willMoveToParentViewController:self]; 138 [self.view addSubview:viewControllerToShow.view]; 139 [viewControllerToShow foregroundControllers]; 140 } 141 142 __weak typeof(self) weakSelf = self; 143 void (^transitionFinished)(void) = ^{ 144 __strong typeof(weakSelf) strongSelf = weakSelf; 145 if (strongSelf) { 146 if (viewControllerToHide) { 147 // backgrounds and then dismisses all modals that are presented by the app 148 [viewControllerToHide backgroundControllers]; 149 [viewControllerToHide dismissViewControllerAnimated:NO completion:nil]; 150 [viewControllerToHide willMoveToParentViewController:nil]; 151 [viewControllerToHide.view removeFromSuperview]; 152 [viewControllerToHide didMoveToParentViewController:nil]; 153 } 154 if (viewControllerToShow) { 155 [viewControllerToShow didMoveToParentViewController:strongSelf]; 156 strongSelf.contentViewController = viewControllerToShow; 157 } 158 [strongSelf.view setNeedsLayout]; 159 strongSelf.isAnimatingAppTransition = NO; 160 if (strongSelf.delegate) { 161 [strongSelf.delegate viewController:strongSelf didNavigateAppToVisible:appRecord]; 162 } 163 } 164 }; 165 166 BOOL animated = (viewControllerToHide && viewControllerToShow); 167 if (animated) { 168 if (viewControllerToHide.contentView) { 169 viewControllerToHide.contentView.transform = CGAffineTransformIdentity; 170 viewControllerToHide.contentView.alpha = 1.0f; 171 } 172 if (viewControllerToShow.contentView) { 173 viewControllerToShow.contentView.transform = CGAffineTransformMakeScale(1.1f, 1.1f); 174 viewControllerToShow.contentView.alpha = 0; 175 } 176 [UIView animateWithDuration:0.3f animations:^{ 177 if (viewControllerToHide.contentView) { 178 viewControllerToHide.contentView.transform = CGAffineTransformMakeScale(0.95f, 0.95f); 179 viewControllerToHide.contentView.alpha = 0.5f; 180 } 181 if (viewControllerToShow.contentView) { 182 viewControllerToShow.contentView.transform = CGAffineTransformIdentity; 183 viewControllerToShow.contentView.alpha = 1.0f; 184 } 185 } completion:^(BOOL finished) { 186 transitionFinished(); 187 }]; 188 } else { 189 transitionFinished(); 190 } 191 } 192} 193 194- (EXHomeAppManager *)_getHomeAppManager 195{ 196 return (EXHomeAppManager *)[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager; 197} 198 199- (void)_maybeResetNuxState 200{ 201 // used by appetize: optionally disable nux 202 BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeDisableNuxDefaultsKey]; 203 if (disableNuxDefaultsValue) { 204 [self setIsNuxFinished:YES]; 205 [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXHomeDisableNuxDefaultsKey]; 206 } 207} 208 209@end 210 211NS_ASSUME_NONNULL_END 212