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