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