1// Copyright 2015-present 650 Industries. All rights reserved. 2 3@import UIKit; 4 5#import "EXAppDelegate.h" 6#import "EXAppViewController.h" 7#import "EXButtonView.h" 8#import "EXHomeAppManager.h" 9#import "EXHomeDiagnosticsViewController.h" 10#import "EXKernel.h" 11#import "EXKernelAppLoader.h" 12#import "EXKernelAppRecord.h" 13#import "EXKernelAppRegistry.h" 14#import "EXKernelDevKeyCommands.h" 15#import "EXKernelLinkingManager.h" 16#import "EXKernelServiceRegistry.h" 17#import "EXMenuGestureRecognizer.h" 18#import "EXMenuViewController.h" 19#import "EXRootViewController.h" 20 21NSString * const kEXHomeDisableNuxDefaultsKey = @"EXKernelDisableNuxDefaultsKey"; 22NSString * const kEXHomeIsNuxFinishedDefaultsKey = @"EXHomeIsNuxFinishedDefaultsKey"; 23 24NS_ASSUME_NONNULL_BEGIN 25 26@interface EXRootViewController () <EXAppBrowserController> 27 28@property (nonatomic, strong) EXMenuViewController *menuViewController; 29@property (nonatomic, assign) BOOL isMenuVisible; 30@property (nonatomic, assign) BOOL isAnimatingMenu; 31@property (nonatomic, assign) BOOL isAnimatingAppTransition; 32@property (nonatomic, strong) EXButtonView *btnMenu; 33 34@end 35 36@implementation EXRootViewController 37 38- (instancetype)init 39{ 40 if (self = [super init]) { 41 [EXKernel sharedInstance].browserController = self; 42 [[NSNotificationCenter defaultCenter] addObserver:self 43 selector:@selector(_updateMenuButtonBehavior) 44 name:kEXKernelDidChangeMenuBehaviorNotification 45 object:nil]; 46 [self _maybeResetNuxState]; 47 } 48 return self; 49} 50 51- (void)viewDidLoad 52{ 53 [super viewDidLoad]; 54 _btnMenu = [[EXButtonView alloc] init]; 55 _btnMenu.hidden = YES; 56 [self.view addSubview:_btnMenu]; 57 EXMenuGestureRecognizer *menuGestureRecognizer = [[EXMenuGestureRecognizer alloc] initWithTarget:self action:@selector(_onMenuGestureRecognized:)]; 58 [((EXAppDelegate *)[UIApplication sharedApplication].delegate).window addGestureRecognizer:menuGestureRecognizer]; 59} 60 61- (void)viewWillLayoutSubviews 62{ 63 [super viewWillLayoutSubviews]; 64 _btnMenu.frame = CGRectMake(0, 0, 48.0f, 48.0f); 65 _btnMenu.center = CGPointMake(self.view.frame.size.width - 36.0f, self.view.frame.size.height - 72.0f); 66 [self.view bringSubviewToFront:_btnMenu]; 67} 68 69#pragma mark - EXViewController 70 71- (void)createRootAppAndMakeVisible 72{ 73 EXHomeAppManager *homeAppManager = [[EXHomeAppManager alloc] init]; 74 EXKernelAppLoader *homeAppLoader = [[EXKernelAppLoader alloc] initWithLocalManifest:[EXHomeAppManager bundledHomeManifest]]; 75 EXKernelAppRecord *homeAppRecord = [[EXKernelAppRecord alloc] initWithAppLoader:homeAppLoader appManager:homeAppManager]; 76 [[EXKernel sharedInstance].appRegistry registerHomeAppRecord:homeAppRecord]; 77 [self moveAppToVisible:homeAppRecord]; 78} 79 80#pragma mark - EXAppBrowserController 81 82- (void)moveAppToVisible:(EXKernelAppRecord *)appRecord 83{ 84 [self _foregroundAppRecord:appRecord]; 85} 86 87- (void)toggleMenuWithCompletion:(void (^ _Nullable)(void))completion 88{ 89 [self setIsMenuVisible:!_isMenuVisible completion:completion]; 90} 91 92- (void)setIsMenuVisible:(BOOL)isMenuVisible completion:(void (^ _Nullable)(void))completion 93{ 94 if (!_menuViewController) { 95 _menuViewController = [[EXMenuViewController alloc] init]; 96 } 97 98 // TODO: ben: can this be more robust? 99 // some third party libs (and core RN) often just look for the root VC and present random crap from it. 100 if (self.presentedViewController && self.presentedViewController != _menuViewController) { 101 [self.presentedViewController dismissViewControllerAnimated:NO completion:nil]; 102 } 103 104 if (isMenuVisible != _isMenuVisible) { 105 if (!_isAnimatingMenu) { 106 _isMenuVisible = isMenuVisible; 107 [self _animateMenuToVisible:_isMenuVisible completion:completion]; 108 } 109 } else { 110 completion(); 111 } 112} 113 114- (void)showDiagnostics 115{ 116 __weak typeof(self) weakSelf = self; 117 [self setIsMenuVisible:NO completion:^{ 118 __strong typeof(weakSelf) strongSelf = weakSelf; 119 if (strongSelf) { 120 EXHomeDiagnosticsViewController *vcDiagnostics = [[EXHomeDiagnosticsViewController alloc] init]; 121 [strongSelf presentViewController:vcDiagnostics animated:NO completion:nil]; 122 } 123 }]; 124} 125 126- (void)showQRReader 127{ 128 [self moveHomeToVisible]; 129 [[self _getHomeAppManager] showQRReader]; 130} 131 132- (void)moveHomeToVisible 133{ 134 __weak typeof(self) weakSelf = self; 135 [self setIsMenuVisible:NO completion:^{ 136 __strong typeof(weakSelf) strongSelf = weakSelf; 137 if (strongSelf) { 138 [strongSelf moveAppToVisible:[EXKernel sharedInstance].appRegistry.homeAppRecord]; 139 } 140 }]; 141} 142 143- (void)refreshVisibleApp 144{ 145 // this is different from Util.reload() 146 // because it can work even on an errored app record (e.g. with no manifest, or with no running bridge). 147 [self setIsMenuVisible:NO completion:nil]; 148 EXKernelAppRecord *visibleApp = [EXKernel sharedInstance].visibleApp; 149 [[EXKernel sharedInstance] logAnalyticsEvent:@"RELOAD_EXPERIENCE" forAppRecord:visibleApp]; 150 NSURL *urlToRefresh = visibleApp.appLoader.manifestUrl; 151 [[EXKernel sharedInstance] createNewAppWithUrl:urlToRefresh initialProps:nil]; 152} 153 154- (void)addHistoryItemWithUrl:(NSURL *)manifestUrl manifest:(NSDictionary *)manifest 155{ 156 [[self _getHomeAppManager] addHistoryItemWithUrl:manifestUrl manifest:manifest]; 157} 158 159- (void)getIsValidHomeManifestToOpen:(NSDictionary *)manifest completion:(void (^)(BOOL isValid))completion 160{ 161 [[self _getHomeAppManager] getIsValidHomeManifestToOpen:manifest completion:completion]; 162} 163 164- (void)getHistoryUrlForExperienceId:(NSString *)experienceId completion:(void (^)(NSString *))completion 165{ 166 return [[self _getHomeAppManager] getHistoryUrlForExperienceId:experienceId completion:completion]; 167} 168 169- (void)setIsNuxFinished:(BOOL)isFinished 170{ 171 [[NSUserDefaults standardUserDefaults] setBool:isFinished forKey:kEXHomeIsNuxFinishedDefaultsKey]; 172 [[NSUserDefaults standardUserDefaults] synchronize]; 173} 174 175- (BOOL)isNuxFinished 176{ 177 return [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeIsNuxFinishedDefaultsKey]; 178} 179 180- (void)appDidFinishLoadingSuccessfully:(EXKernelAppRecord *)appRecord 181{ 182 // show nux if needed 183 if (!self.isNuxFinished 184 && appRecord == [EXKernel sharedInstance].visibleApp 185 && appRecord != [EXKernel sharedInstance].appRegistry.homeAppRecord 186 && !self.isMenuVisible) { 187 [self setIsMenuVisible:YES completion:nil]; 188 } 189 190 // check button availability when any new app loads 191 [self _updateMenuButtonBehavior]; 192} 193 194#pragma mark - internal 195 196- (void)_foregroundAppRecord:(EXKernelAppRecord *)appRecord 197{ 198 if (_isAnimatingAppTransition) { 199 return; 200 } 201 EXAppViewController *viewControllerToShow = appRecord.viewController; 202 EXAppViewController *viewControllerToHide; 203 if (viewControllerToShow != self.contentViewController) { 204 _isAnimatingAppTransition = YES; 205 if (self.contentViewController) { 206 viewControllerToHide = (EXAppViewController *)self.contentViewController; 207 } 208 if (viewControllerToShow) { 209 [viewControllerToShow willMoveToParentViewController:self]; 210 [self.view addSubview:viewControllerToShow.view]; 211 } 212 213 __weak typeof(self) weakSelf = self; 214 void (^transitionFinished)(void) = ^{ 215 __strong typeof(weakSelf) strongSelf = weakSelf; 216 if (strongSelf) { 217 if (viewControllerToHide) { 218 [viewControllerToHide willMoveToParentViewController:nil]; 219 [viewControllerToHide.view removeFromSuperview]; 220 [viewControllerToHide didMoveToParentViewController:nil]; 221 } 222 if (viewControllerToShow) { 223 [viewControllerToShow didMoveToParentViewController:strongSelf]; 224 strongSelf.contentViewController = viewControllerToShow; 225 } 226 [strongSelf.view setNeedsLayout]; 227 strongSelf.isAnimatingAppTransition = NO; 228 if (strongSelf.delegate) { 229 [strongSelf.delegate viewController:strongSelf didNavigateAppToVisible:appRecord]; 230 } 231 } 232 }; 233 234 BOOL animated = (viewControllerToHide && viewControllerToShow); 235 if (animated) { 236 if (viewControllerToHide.contentView) { 237 viewControllerToHide.contentView.transform = CGAffineTransformIdentity; 238 viewControllerToHide.contentView.alpha = 1.0f; 239 } 240 if (viewControllerToShow.contentView) { 241 viewControllerToShow.contentView.transform = CGAffineTransformMakeScale(1.1f, 1.1f); 242 viewControllerToShow.contentView.alpha = 0; 243 } 244 [UIView animateWithDuration:0.3f animations:^{ 245 if (viewControllerToHide.contentView) { 246 viewControllerToHide.contentView.transform = CGAffineTransformMakeScale(0.95f, 0.95f); 247 viewControllerToHide.contentView.alpha = 0.5f; 248 } 249 if (viewControllerToShow.contentView) { 250 viewControllerToShow.contentView.transform = CGAffineTransformIdentity; 251 viewControllerToShow.contentView.alpha = 1.0f; 252 } 253 } completion:^(BOOL finished) { 254 transitionFinished(); 255 }]; 256 } else { 257 transitionFinished(); 258 } 259 } 260} 261 262- (void)_animateMenuToVisible:(BOOL)visible completion:(void (^ _Nullable)(void))completion 263{ 264 _isAnimatingMenu = YES; 265 __weak typeof(self) weakSelf = self; 266 if (visible) { 267 [_menuViewController willMoveToParentViewController:self]; 268 [self.view addSubview:_menuViewController.view]; 269 _menuViewController.view.alpha = 0.0f; 270 _menuViewController.view.transform = CGAffineTransformMakeScale(1.1f, 1.1f); 271 [UIView animateWithDuration:0.1f animations:^{ 272 _menuViewController.view.alpha = 1.0f; 273 _menuViewController.view.transform = CGAffineTransformIdentity; 274 } completion:^(BOOL finished) { 275 __strong typeof(weakSelf) strongSelf = weakSelf; 276 if (strongSelf) { 277 strongSelf.isAnimatingMenu = NO; 278 [strongSelf.menuViewController didMoveToParentViewController:self]; 279 if (completion) { 280 completion(); 281 } 282 } 283 }]; 284 } else { 285 _menuViewController.view.alpha = 1.0f; 286 [UIView animateWithDuration:0.1f animations:^{ 287 _menuViewController.view.alpha = 0.0f; 288 } completion:^(BOOL finished) { 289 __strong typeof(weakSelf) strongSelf = weakSelf; 290 if (strongSelf) { 291 strongSelf.isAnimatingMenu = NO; 292 [strongSelf.menuViewController willMoveToParentViewController:nil]; 293 [strongSelf.menuViewController.view removeFromSuperview]; 294 [strongSelf.menuViewController didMoveToParentViewController:nil]; 295 if (completion) { 296 completion(); 297 } 298 } 299 }]; 300 } 301} 302 303- (EXHomeAppManager *)_getHomeAppManager 304{ 305 return (EXHomeAppManager *)[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager; 306} 307 308- (void)_maybeResetNuxState 309{ 310 // used by appetize: optionally disable nux 311 BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeDisableNuxDefaultsKey]; 312 if (disableNuxDefaultsValue) { 313 [self setIsNuxFinished:YES]; 314 [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXHomeDisableNuxDefaultsKey]; 315 } 316} 317 318- (void)_updateMenuButtonBehavior 319{ 320 BOOL shouldShowButton = [[EXKernelDevKeyCommands sharedInstance] isLegacyMenuButtonAvailable]; 321 dispatch_async(dispatch_get_main_queue(), ^{ 322 _btnMenu.hidden = !shouldShowButton; 323 }); 324} 325 326- (void)_onMenuGestureRecognized:(EXMenuGestureRecognizer *)sender 327{ 328 if (sender.state == UIGestureRecognizerStateEnded) { 329 [[EXKernel sharedInstance] switchTasks]; 330 } 331} 332 333@end 334 335NS_ASSUME_NONNULL_END 336