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