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