1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import <React/RCTRootView.h> 4 5#import "EXDevMenuViewController.h" 6#import "EXDevMenuManager.h" 7#import "EXKernel.h" 8#import "EXAppLoader.h" 9#import "EXKernelAppRegistry.h" 10#import "EXUtil.h" 11 12@interface EXDevMenuViewController () 13 14@property (nonatomic, strong) RCTRootView *reactRootView; 15@property (nonatomic, assign) BOOL hasCalledJSLoadedNotification; 16 17@end 18 19@interface RCTRootView (EXDevMenuView) 20 21- (void)javaScriptDidLoad:(NSNotification *)notification; 22- (void)hideLoadingView; 23 24@end 25 26@implementation EXDevMenuViewController 27 28# pragma mark - UIViewController 29 30- (void)viewDidLoad 31{ 32 [super viewDidLoad]; 33 34 [self _maybeRebuildRootView]; 35 [self.view addSubview:_reactRootView]; 36} 37 38- (UIRectEdge)edgesForExtendedLayout 39{ 40 return UIRectEdgeNone; 41} 42 43- (BOOL)extendedLayoutIncludesOpaqueBars 44{ 45 return YES; 46} 47 48- (void)viewWillLayoutSubviews 49{ 50 [super viewWillLayoutSubviews]; 51 _reactRootView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); 52} 53 54- (void)viewWillAppear:(BOOL)animated 55{ 56 [super viewWillAppear:animated]; 57 [self _maybeRebuildRootView]; 58 [self _forceRootViewToRenderHack]; 59 [_reactRootView becomeFirstResponder]; 60} 61 62- (BOOL)shouldAutorotate 63{ 64 return YES; 65} 66 67/** 68 * Overrides UIViewController's method that returns interface orientations that the view controller supports. 69 * If EXDevMenuViewController is currently shown we want to use its supported orientations so the UI rotates 70 * when we open the dev menu while in the unsupported orientation. 71 * Otherwise, returns interface orientations supported by the current experience. 72 */ 73- (UIInterfaceOrientationMask)supportedInterfaceOrientations 74{ 75 return UIInterfaceOrientationMaskPortrait; 76} 77 78/** 79 * Same case as above with `supportedInterfaceOrientations` method. 80 * If we don't override this, we can get incorrect orientation while changing device orientation when the dev menu is visible. 81 */ 82- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation 83{ 84 return UIInterfaceOrientationPortrait; 85} 86 87#pragma mark - API 88 89 90 91#pragma mark - internal 92 93- (NSDictionary *)_getInitialPropsForVisibleApp 94{ 95 EXKernelAppRecord *visibleApp = [EXKernel sharedInstance].visibleApp; 96 NSDictionary *task = @{ 97 @"manifestUrl": visibleApp.appLoader.manifestUrl.absoluteString, 98 @"manifest": (visibleApp.appLoader.manifest) ? visibleApp.appLoader.manifest : [NSNull null], 99 }; 100 101 return @{ 102 @"task": task, 103 @"uuid": [[NSUUID UUID] UUIDString], // include randomness to force the component to rerender 104 }; 105} 106 107// RCTRootView assumes it is created on a loading bridge. 108// in our case, the bridge has usually already loaded. so we need to prod the view. 109- (void)_forceRootViewToRenderHack 110{ 111 if (!_hasCalledJSLoadedNotification) { 112 RCTBridge *mainBridge = [[EXDevMenuManager sharedInstance] mainBridge]; 113 NSNotification *notif = [[NSNotification alloc] initWithName:RCTJavaScriptDidLoadNotification 114 object:nil 115 userInfo:@{ @"bridge": mainBridge }]; 116 [_reactRootView javaScriptDidLoad:notif]; 117 _hasCalledJSLoadedNotification = YES; 118 } 119} 120 121- (void)_maybeRebuildRootView 122{ 123 RCTBridge *mainBridge = [[EXDevMenuManager sharedInstance] mainBridge]; 124 125 // Main bridge might change if the home bridge restarted for some reason (e.g. due to an error) 126 if (!_reactRootView || _reactRootView.bridge != mainBridge) { 127 if (_reactRootView) { 128 [_reactRootView removeFromSuperview]; 129 _reactRootView = nil; 130 } 131 _hasCalledJSLoadedNotification = NO; 132 133 _reactRootView = [[RCTRootView alloc] initWithBridge:mainBridge moduleName:@"HomeMenu" initialProperties:[self _getInitialPropsForVisibleApp]]; 134 _reactRootView.frame = self.view.bounds; 135 136 // By default react root view has white background, 137 // however devmenu's bottom sheet looks better with partially visible experience. 138 _reactRootView.backgroundColor = [UIColor clearColor]; 139 140 if ([self isViewLoaded]) { 141 [self.view addSubview:_reactRootView]; 142 [self.view setNeedsLayout]; 143 } 144 } else if (_reactRootView) { 145 _reactRootView.appProperties = [self _getInitialPropsForVisibleApp]; 146 } 147} 148 149@end 150