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