1// Copyright 2015-present 650 Industries. All rights reserved.
2
3@import ObjectiveC;
4
5#import "EXViewController.h"
6#import "EXErrorView.h"
7#import "EXFileDownloader.h"
8#import "EXKernel.h"
9#import "EXKernelUtil.h"
10#import "EXScreenOrientationManager.h"
11
12#import <React/RCTDevLoadingView.h>
13#import <React/RCTRootView.h>
14
15NS_ASSUME_NONNULL_BEGIN
16
17@interface EXViewController () <EXErrorViewDelegate>
18
19@property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
20@property (nonatomic, strong) EXErrorView *errorView;
21
22@end
23
24@implementation EXViewController
25
26#pragma mark - Lifecycle
27
28- (instancetype)initWithLaunchOptions:(NSDictionary *)launchOptions
29{
30  if (self = [super init]) {
31    _appManager = [[EXKernelReactAppManager alloc] initWithLaunchOptions:launchOptions];
32    _appManager.delegate = self;
33    [[EXKernel sharedInstance] registerRootExponentViewController:self];
34  }
35  return self;
36}
37
38- (void)viewDidLoad
39{
40  [super viewDidLoad];
41  _loadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
42  _loadingIndicator.hidesWhenStopped = YES;
43}
44
45- (BOOL)shouldAutorotate
46{
47  return YES;
48}
49
50- (UIInterfaceOrientationMask)supportedInterfaceOrientations
51{
52  return [[EXKernel sharedInstance].serviceRegistry.screenOrientationManager supportedInterfaceOrientationsForForegroundExperience];
53}
54
55#pragma mark - Public
56
57- (void)showErrorWithType:(EXFatalErrorType)type error:(nullable NSError *)error
58{
59  EXAssertMainThread();
60  if (_errorView && _contentView == _errorView) {
61    // already showing, just update
62    _errorView.type = type;
63    _errorView.error = error;
64  } {
65    [_contentView removeFromSuperview];
66    if (!_errorView) {
67      _errorView = [[EXErrorView alloc] initWithFrame:self.view.bounds];
68      _errorView.delegate = self;
69    }
70    _errorView.type = type;
71    _errorView.error = error;
72    _contentView = _errorView;
73    [self.view addSubview:_contentView];
74  }
75}
76
77- (void)loadReactApplication
78{
79  [_appManager reload];
80}
81
82- (void)setIsLoading:(BOOL)isLoading
83{
84  _isLoading = isLoading;
85  if (_isLoading) {
86    [_loadingIndicator startAnimating];
87  } else {
88    [_loadingIndicator stopAnimating];
89  }
90}
91
92- (NSDictionary *)launchOptions
93{
94  return self.appManager.launchOptions;
95}
96
97#pragma mark - EXReactAppManagerDelegate
98
99- (void)reactAppManagerDidInitApp:(EXReactAppManager *)appManager
100{
101  UIView *reactView = appManager.reactRootView;
102  reactView.frame = self.view.bounds;
103  reactView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
104  reactView.backgroundColor = [UIColor clearColor];
105
106  [_contentView removeFromSuperview];
107  _contentView = reactView;
108  [self.view addSubview:_contentView];
109  [reactView becomeFirstResponder];
110
111  self.isLoading = YES;
112}
113
114- (void)reactAppManagerDidDestroyApp:(EXReactAppManager *)appManager
115{
116
117}
118
119- (void)reactAppManager:(EXReactAppManager *)appManager failedToDownloadBundleWithError:(NSError *)error
120{
121  BOOL isNetworkError = ([error.domain isEqualToString:(NSString *)kCFErrorDomainCFNetwork] ||
122                         [error.domain isEqualToString:EXNetworkErrorDomain]);
123  if (isNetworkError &&
124      error.code == kCFURLErrorNotConnectedToInternet) {
125    // show a human-readable reachability error
126    __weak typeof(self) weakSelf = self;
127    dispatch_async(dispatch_get_main_queue(), ^{
128      [weakSelf showErrorWithType:kEXFatalErrorTypeLoading error:error];
129    });
130  }
131}
132
133- (void)reactAppManagerStartedLoadingJavaScript:(EXReactAppManager *)appManager
134{
135  EXAssertMainThread();
136  self.isLoading = YES;
137}
138
139- (void)reactAppManager:(EXReactAppManager *)appManager loadedJavaScriptWithProgress:(RCTLoadingProgress *)progress
140{
141}
142
143- (void)reactAppManagerFinishedLoadingJavaScript:(EXReactAppManager *)appManager
144{
145  EXAssertMainThread();
146  self.isLoading = NO;
147}
148
149- (void)reactAppManager:(EXReactAppManager *)appManager failedToLoadJavaScriptWithError:(NSError *)error
150{
151  EXAssertMainThread();
152  self.isLoading = NO;
153  [self showErrorWithType:kEXFatalErrorTypeLoading error:error];
154}
155
156- (void)reactAppManagerDidForeground:(EXReactAppManager *)appManager
157{
158  dispatch_async(dispatch_get_main_queue(), ^{
159    [self _enforceKernelOrientation];
160  });
161}
162
163#pragma mark - Delegate
164
165- (void)errorViewDidSelectRetry:(EXErrorView *)errorView
166{
167  // if the app launched with some options, clear them-- this is no longer a new launch,
168  // and it's possible that these options were what caused the error (e.g. a bad initial url)
169  _appManager.launchOptions = nil;
170
171  [self loadReactApplication];
172}
173
174#pragma mark - Internal
175
176- (void)_enforceKernelOrientation
177{
178  EXAssertMainThread();
179  UIInterfaceOrientationMask mask = [self supportedInterfaceOrientations];
180  UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
181  if (mask == UIInterfaceOrientationMaskLandscape && (currentOrientation == UIDeviceOrientationPortrait)) {
182    [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft) forKey:@"orientation"];
183  } else if (mask == UIInterfaceOrientationMaskPortrait && (currentOrientation != UIDeviceOrientationPortrait)) {
184    [[UIDevice currentDevice] setValue:@(UIDeviceOrientationPortrait) forKey:@"orientation"];
185  }
186  [UIViewController attemptRotationToDeviceOrientation];
187}
188
189@end
190
191NS_ASSUME_NONNULL_END
192