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