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