13217e243SBen Roth// Copyright 2015-present 650 Industries. All rights reserved. 23217e243SBen Roth 33217e243SBen Roth@import UIKit; 43217e243SBen Roth 57c3ed0c0SDouglas Lowder#import "EXAbstractLoader.h" 600fcd3c6SBen Roth#import "EXAppViewController.h" 79ef743bcSBartłomiej Bukowski#import "EXAppLoadingProgressWindowController.h" 827700081SBartłomiej Bukowski#import "EXAppLoadingCancelView.h" 927700081SBartłomiej Bukowski#import "EXManagedAppSplashScreenViewProvider.h" 1027700081SBartłomiej Bukowski#import "EXManagedAppSplashScreenConfigurationBuilder.h" 119fcddb20Sandy#import "EXManagedAppSplashScreenViewController.h" 1227700081SBartłomiej Bukowski#import "EXHomeAppSplashScreenViewProvider.h" 1300fcd3c6SBen Roth#import "EXEnvironment.h" 1400fcd3c6SBen Roth#import "EXErrorRecoveryManager.h" 1500fcd3c6SBen Roth#import "EXErrorView.h" 1600fcd3c6SBen Roth#import "EXFileDownloader.h" 1700fcd3c6SBen Roth#import "EXKernel.h" 183217e243SBen Roth#import "EXKernelUtil.h" 1900fcd3c6SBen Roth#import "EXReactAppManager.h" 20ee295080SŁukasz Kosmaty#import "EXVersions.h" 2130fccd70SEric Samelson#import "EXUpdatesManager.h" 223691b080SBrent Vatne#import "EXUtil.h" 2327700081SBartłomiej Bukowski 2427700081SBartłomiej Bukowski#import <EXSplashScreen/EXSplashScreenService.h> 2527700081SBartłomiej Bukowski#import <React/RCTUtils.h> 26ea3f1d02STomasz Sapeta#import <ExpoModulesCore/EXModuleRegistryProvider.h> 27ee295080SŁukasz Kosmaty 2883bf929eSBartłomiej Bukowski#import <React/RCTAppearance.h> 29af2ec015STomasz Sapeta#if defined(INCLUDES_VERSIONED_CODE) && __has_include(<ABI49_0_0React/ABI49_0_0RCTAppearance.h>) 30af2ec015STomasz Sapeta#import <ABI49_0_0React/ABI49_0_0RCTAppearance.h> 31af2ec015STomasz Sapeta#endif 32fe5cfb17STomasz Sapeta#if defined(INCLUDES_VERSIONED_CODE) && __has_include(<ABI48_0_0React/ABI48_0_0RCTAppearance.h>) 33fe5cfb17STomasz Sapeta#import <ABI48_0_0React/ABI48_0_0RCTAppearance.h> 34fe5cfb17STomasz Sapeta#endif 35753557f6STomasz Sapeta#if defined(INCLUDES_VERSIONED_CODE) && __has_include(<ABI47_0_0React/ABI47_0_0RCTAppearance.h>) 36753557f6STomasz Sapeta#import <ABI47_0_0React/ABI47_0_0RCTAppearance.h> 37753557f6STomasz Sapeta#endif 3866169681SBen Roth 3912075537Saleqsio#if defined(EX_DETACHED) 4012075537Saleqsio#import "ExpoKit-Swift.h" 4112075537Saleqsio#else 4212075537Saleqsio#import "Expo_Go-Swift.h" 4312075537Saleqsio#endif // defined(EX_DETACHED) 4412075537Saleqsio 450b66f7ddSWill Schurman@import EXManifests; 4612075537Saleqsio 470b761b65SWojciech Dróżdż@import ExpoScreenOrientation; 480b761b65SWojciech Dróżdż 49a9a91078SEric Samelson#define EX_INTERFACE_ORIENTATION_USE_MANIFEST 0 50a9a91078SEric Samelson 514f9de556SBen Roth// when we encounter an error and auto-refresh, we may actually see a series of errors. 524f9de556SBen Roth// we only want to trigger refresh once, so we debounce refresh on a timer. 53e7ac6e4fSBen Rothconst CGFloat kEXAutoReloadDebounceSeconds = 0.1; 544f9de556SBen Roth 554f9de556SBen Roth// in development only, some errors can happen before we even start loading 564f9de556SBen Roth// (e.g. certain packager errors, such as an invalid bundle url) 574f9de556SBen Roth// and we want to make sure not to cover the error with a loading view or other chrome. 584f9de556SBen Rothconst CGFloat kEXDevelopmentErrorCoolDownSeconds = 0.1; 594f9de556SBen Roth 608b74a44fSŁukasz Kosmaty// copy of RNScreens protocol 618b74a44fSŁukasz Kosmaty@protocol EXKernelRNSScreenWindowTraits 628b74a44fSŁukasz Kosmaty 638b74a44fSŁukasz Kosmaty+ (BOOL)shouldAskScreensForScreenOrientationInViewController:(UIViewController *)vc; 648b74a44fSŁukasz Kosmaty 658b74a44fSŁukasz Kosmaty@end 668b74a44fSŁukasz Kosmaty 673217e243SBen RothNS_ASSUME_NONNULL_BEGIN 683217e243SBen Roth 691e73a75eSBen Roth@interface EXAppViewController () 7027700081SBartłomiej Bukowski <EXReactAppManagerUIDelegate, EXAppLoaderDelegate, EXErrorViewDelegate, EXAppLoadingCancelViewDelegate> 713217e243SBen Roth 723217e243SBen Roth@property (nonatomic, assign) BOOL isLoading; 732852f59dSDouglas Lowder@property (atomic, assign) BOOL isBridgeAlreadyLoading; 743217e243SBen Roth@property (nonatomic, weak) EXKernelAppRecord *appRecord; 753217e243SBen Roth@property (nonatomic, strong) EXErrorView *errorView; 76e7ac6e4fSBen Roth@property (nonatomic, strong) NSTimer *tmrAutoReloadDebounce; 774f9de556SBen Roth@property (nonatomic, strong) NSDate *dtmLastFatalErrorShown; 78ce63205bSTomasz Sapeta@property (nonatomic, strong) NSMutableArray<UIViewController *> *backgroundedControllers; 793217e243SBen Roth 8027700081SBartłomiej Bukowski@property (nonatomic, assign) BOOL isStandalone; 8127700081SBartłomiej Bukowski@property (nonatomic, assign) BOOL isHomeApp; 82*24857eeaSWojciech Dróżdż@property (nonatomic, assign) UIInterfaceOrientation previousInterfaceOrientation; 8327700081SBartłomiej Bukowski 849ef743bcSBartłomiej Bukowski/* 859ef743bcSBartłomiej Bukowski * Controller for handling all messages from bundler/fetcher. 869ef743bcSBartłomiej Bukowski * It shows another UIWindow with text and percentage progress. 879ef743bcSBartłomiej Bukowski * Enabled only in managed workflow or home when in development mode. 889ef743bcSBartłomiej Bukowski * It should appear once manifest is fetched. 899ef743bcSBartłomiej Bukowski */ 909ef743bcSBartłomiej Bukowski@property (nonatomic, strong, nonnull) EXAppLoadingProgressWindowController *appLoadingProgressWindowController; 919ef743bcSBartłomiej Bukowski 9227700081SBartłomiej Bukowski/** 9327700081SBartłomiej Bukowski * SplashScreenViewProvider that is used only in managed workflow app. 9427700081SBartłomiej Bukowski * Managed app does not need any specific SplashScreenViewProvider as it uses generic one povided by the SplashScreen module. 954d7a9b36SBartłomiej Bukowski * See also EXHomeAppSplashScreenViewProvider in self.viewDidLoad 9627700081SBartłomiej Bukowski */ 9727700081SBartłomiej Bukowski@property (nonatomic, strong, nullable) EXManagedAppSplashScreenViewProvider *managedAppSplashScreenViewProvider; 983c604617Sandy@property (nonatomic, strong, nullable) EXManagedAppSplashScreenViewController *managedSplashScreenController; 9927700081SBartłomiej Bukowski 10027700081SBartłomiej Bukowski/* 101dc898499SJames Ide * This view is available in managed apps run in Expo Go only. 10227700081SBartłomiej Bukowski * It is shown only before any managed app manifest is delivered by the app loader. 10327700081SBartłomiej Bukowski */ 10427700081SBartłomiej Bukowski@property (nonatomic, strong, nullable) EXAppLoadingCancelView *appLoadingCancelView; 10527700081SBartłomiej Bukowski 1063217e243SBen Roth@end 1073217e243SBen Roth 1083217e243SBen Roth@implementation EXAppViewController 1093217e243SBen Roth 1103217e243SBen Roth#pragma mark - Lifecycle 1113217e243SBen Roth 1123217e243SBen Roth- (instancetype)initWithAppRecord:(EXKernelAppRecord *)record 1133217e243SBen Roth{ 1143217e243SBen Roth if (self = [super init]) { 1153217e243SBen Roth _appRecord = record; 11627700081SBartłomiej Bukowski _isStandalone = [EXEnvironment sharedEnvironment].isDetached; 117*24857eeaSWojciech Dróżdż // For iPads traitCollectionDidChange will not be called (it's always in the same size class). It is necessary 118*24857eeaSWojciech Dróżdż // to init it in here, so it's possible to return it in the didUpdateDimensionsEvent of the module 119*24857eeaSWojciech Dróżdż if (ScreenOrientationRegistry.shared.currentTraitCollection == nil) { 120*24857eeaSWojciech Dróżdż [ScreenOrientationRegistry.shared traitCollectionDidChangeTo:self.traitCollection]; 121*24857eeaSWojciech Dróżdż } 1223217e243SBen Roth } 1233217e243SBen Roth return self; 1243217e243SBen Roth} 1253217e243SBen Roth 1263217e243SBen Roth- (void)dealloc 1273217e243SBen Roth{ 128e7ac6e4fSBen Roth [self _invalidateRecoveryTimer]; 1293217e243SBen Roth [[NSNotificationCenter defaultCenter] removeObserver:self]; 1303217e243SBen Roth} 1313217e243SBen Roth 1323217e243SBen Roth- (void)viewDidLoad 1333217e243SBen Roth{ 1343217e243SBen Roth [super viewDidLoad]; 1353691b080SBrent Vatne 13627700081SBartłomiej Bukowski // EXKernel.appRegistry.homeAppRecord does not contain any homeAppRecord until this point, 1374d7a9b36SBartłomiej Bukowski // therefore we cannot move this property initialization to the constructor/initializer 13827700081SBartłomiej Bukowski _isHomeApp = _appRecord == [EXKernel sharedInstance].appRegistry.homeAppRecord; 13927700081SBartłomiej Bukowski 14027700081SBartłomiej Bukowski // show LoadingCancelView in managed apps only 14127700081SBartłomiej Bukowski if (!self.isStandalone && !self.isHomeApp) { 14227700081SBartłomiej Bukowski self.appLoadingCancelView = [EXAppLoadingCancelView new]; 14327700081SBartłomiej Bukowski // if home app is available then LoadingCancelView can show `go to home` button 14427700081SBartłomiej Bukowski if ([EXKernel sharedInstance].appRegistry.homeAppRecord) { 14527700081SBartłomiej Bukowski self.appLoadingCancelView.delegate = self; 14627700081SBartłomiej Bukowski } 14727700081SBartłomiej Bukowski [self.view addSubview:self.appLoadingCancelView]; 14827700081SBartłomiej Bukowski [self.view bringSubviewToFront:self.appLoadingCancelView]; 14927700081SBartłomiej Bukowski } 15027700081SBartłomiej Bukowski 15157bba33fSEric Samelson // show LoadingProgressWindow in the development client for all apps other than production home 15257bba33fSEric Samelson BOOL isProductionHomeApp = self.isHomeApp && ![EXEnvironment sharedEnvironment].isDebugXCodeScheme; 15357bba33fSEric Samelson self.appLoadingProgressWindowController = [[EXAppLoadingProgressWindowController alloc] initWithEnabled:!self.isStandalone && !isProductionHomeApp]; 15427700081SBartłomiej Bukowski 15527700081SBartłomiej Bukowski // show SplashScreen in standalone apps and home app only 15627700081SBartłomiej Bukowski // SplashScreen for managed is shown once the manifest is available 15727700081SBartłomiej Bukowski if (self.isHomeApp) { 1584d7a9b36SBartłomiej Bukowski EXHomeAppSplashScreenViewProvider *homeAppSplashScreenViewProvider = [EXHomeAppSplashScreenViewProvider new]; 159cdf87a03SStanisław Chmiela [self _showSplashScreenWithProvider:homeAppSplashScreenViewProvider]; 16027700081SBartłomiej Bukowski } else if (self.isStandalone) { 161cdf87a03SStanisław Chmiela [self _showSplashScreenWithProvider:[EXSplashScreenViewNativeProvider new]]; 16227700081SBartłomiej Bukowski } 16327700081SBartłomiej Bukowski 1642f8e7579SBen Roth self.view.backgroundColor = [UIColor whiteColor]; 1653217e243SBen Roth _appRecord.appManager.delegate = self; 1663217e243SBen Roth self.isLoading = YES; 1673217e243SBen Roth} 1683217e243SBen Roth 1693217e243SBen Roth- (void)viewDidAppear:(BOOL)animated 1703217e243SBen Roth{ 1713217e243SBen Roth [super viewDidAppear:animated]; 1723217e243SBen Roth if (_appRecord && _appRecord.status == kEXKernelAppRecordStatusNew) { 1733217e243SBen Roth _appRecord.appLoader.delegate = self; 1741e73a75eSBen Roth _appRecord.appLoader.dataSource = _appRecord.appManager; 1753217e243SBen Roth [self refresh]; 1763217e243SBen Roth } 1773217e243SBen Roth} 1783217e243SBen Roth 1793217e243SBen Roth- (BOOL)shouldAutorotate 1803217e243SBen Roth{ 1813217e243SBen Roth return YES; 1823217e243SBen Roth} 1833217e243SBen Roth 1843217e243SBen Roth- (void)viewWillLayoutSubviews 1853217e243SBen Roth{ 1863217e243SBen Roth [super viewWillLayoutSubviews]; 18727700081SBartłomiej Bukowski if (_appLoadingCancelView) { 18827700081SBartłomiej Bukowski _appLoadingCancelView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); 1893217e243SBen Roth } 1903217e243SBen Roth if (_contentView) { 191edc8619eSBen Roth _contentView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); 1923217e243SBen Roth } 1933217e243SBen Roth} 1943217e243SBen Roth 19527700081SBartłomiej Bukowski- (void)viewWillDisappear:(BOOL)animated 19627700081SBartłomiej Bukowski{ 19727700081SBartłomiej Bukowski [_appLoadingProgressWindowController hide]; 19827700081SBartłomiej Bukowski [super viewWillDisappear:animated]; 19927700081SBartłomiej Bukowski} 20027700081SBartłomiej Bukowski 201c810f790STomasz Sapeta/** 202c810f790STomasz Sapeta * Force presented view controllers to use the same user interface style. 203c810f790STomasz Sapeta */ 204b9d8b772STomasz Sapeta- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion 205b9d8b772STomasz Sapeta{ 206b9d8b772STomasz Sapeta [super presentViewController:viewControllerToPresent animated:flag completion:completion]; 207b9d8b772STomasz Sapeta [self _overrideUserInterfaceStyleOf:viewControllerToPresent]; 208b9d8b772STomasz Sapeta} 209b9d8b772STomasz Sapeta 210c810f790STomasz Sapeta/** 211c810f790STomasz Sapeta * Force child view controllers to use the same user interface style. 212c810f790STomasz Sapeta */ 213c810f790STomasz Sapeta- (void)addChildViewController:(UIViewController *)childController 214c810f790STomasz Sapeta{ 215c810f790STomasz Sapeta [super addChildViewController:childController]; 216c810f790STomasz Sapeta [self _overrideUserInterfaceStyleOf:childController]; 217c810f790STomasz Sapeta} 218c810f790STomasz Sapeta 2193217e243SBen Roth#pragma mark - Public 2203217e243SBen Roth 2213217e243SBen Roth- (void)maybeShowError:(NSError *)error 2223217e243SBen Roth{ 2233217e243SBen Roth self.isLoading = NO; 224e7ac6e4fSBen Roth if ([self _willAutoRecoverFromError:error]) { 225e7ac6e4fSBen Roth return; 226e7ac6e4fSBen Roth } 227cd3513e8SBen Roth if (error && ![error isKindOfClass:[NSError class]]) { 228cd3513e8SBen Roth#if DEBUG 229cd3513e8SBen Roth NSAssert(NO, @"AppViewController error handler was called on an object that isn't an NSError"); 230cd3513e8SBen Roth#endif 231cd3513e8SBen Roth return; 232cd3513e8SBen Roth } 2336e2eab62SEric Samelson 2346e2eab62SEric Samelson // we don't ever want to show any Expo UI in a production standalone app, so hard crash 2356e2eab62SEric Samelson if ([EXEnvironment sharedEnvironment].isDetached && ![_appRecord.appManager enablesDeveloperTools]) { 2366e2eab62SEric Samelson NSException *e = [NSException exceptionWithName:@"ExpoFatalError" 2376e2eab62SEric Samelson reason:[NSString stringWithFormat:@"Expo encountered a fatal error: %@", [error localizedDescription]] 2386e2eab62SEric Samelson userInfo:@{NSUnderlyingErrorKey: error}]; 2396e2eab62SEric Samelson @throw e; 2406e2eab62SEric Samelson } 2416e2eab62SEric Samelson 2424033a60bSBen Roth NSString *domain = (error && error.domain) ? error.domain : @""; 243558783dfSEric Samelson BOOL isNetworkError = ([domain isEqualToString:(NSString *)kCFErrorDomainCFNetwork] || [domain isEqualToString:NSURLErrorDomain] || [domain isEqualToString:EXNetworkErrorDomain]); 2444033a60bSBen Roth 2453217e243SBen Roth if (isNetworkError) { 2463217e243SBen Roth // show a human-readable reachability error 2473217e243SBen Roth dispatch_async(dispatch_get_main_queue(), ^{ 2483217e243SBen Roth [self _showErrorWithType:kEXFatalErrorTypeLoading error:error]; 2493217e243SBen Roth }); 2504033a60bSBen Roth } else if ([domain isEqualToString:@"JSServer"] && [_appRecord.appManager enablesDeveloperTools]) { 2513217e243SBen Roth // RCTRedBox already handled this 2524033a60bSBen Roth } else if ([domain rangeOfString:RCTErrorDomain].length > 0 && [_appRecord.appManager enablesDeveloperTools]) { 2533217e243SBen Roth // RCTRedBox already handled this 2543217e243SBen Roth } else { 2553217e243SBen Roth dispatch_async(dispatch_get_main_queue(), ^{ 2563217e243SBen Roth [self _showErrorWithType:kEXFatalErrorTypeException error:error]; 2573217e243SBen Roth }); 2583217e243SBen Roth } 2593217e243SBen Roth} 2603217e243SBen Roth 2613217e243SBen Roth- (void)refresh 2623217e243SBen Roth{ 2633217e243SBen Roth self.isLoading = YES; 264b0185235SEric Samelson self.isBridgeAlreadyLoading = NO; 265e7ac6e4fSBen Roth [self _invalidateRecoveryTimer]; 2663217e243SBen Roth [_appRecord.appLoader request]; 2673217e243SBen Roth} 2683217e243SBen Roth 2699777fddbSEric Samelson- (void)reloadFromCache 2709777fddbSEric Samelson{ 2719777fddbSEric Samelson self.isLoading = YES; 2729777fddbSEric Samelson self.isBridgeAlreadyLoading = NO; 2739777fddbSEric Samelson [self _invalidateRecoveryTimer]; 2749777fddbSEric Samelson [_appRecord.appLoader requestFromCache]; 2759777fddbSEric Samelson} 2769777fddbSEric Samelson 27712075537Saleqsio- (bool)_readSupportsRTLFromManifest:(EXManifestsManifest *)manifest 27812075537Saleqsio{ 27994af3edaSWill Schurman return manifest.supportsRTL; 28012075537Saleqsio} 28112075537Saleqsio 282e7ac6e4fSBen Roth- (void)appStateDidBecomeActive 2833217e243SBen Roth{ 28412075537Saleqsio if (_isHomeApp) { 28512075537Saleqsio [EXTextDirectionController setSupportsRTL:false]; 28612075537Saleqsio } else if(_appRecord.appLoader.manifest != nil) { 28712075537Saleqsio [EXTextDirectionController setSupportsRTL:[self _readSupportsRTLFromManifest:_appRecord.appLoader.manifest]]; 28812075537Saleqsio } 2893217e243SBen Roth dispatch_async(dispatch_get_main_queue(), ^{ 29056fb41fdSBrent Vatne // Reset the root view background color and window color if we switch between Expo home and project 2911f6f211eSEvan Bacon [self _setBackgroundColor]; 2923217e243SBen Roth }); 2933217e243SBen Roth} 2943217e243SBen Roth 295e7ac6e4fSBen Roth- (void)appStateDidBecomeInactive 2963217e243SBen Roth{ 2973217e243SBen Roth} 2983217e243SBen Roth 29927700081SBartłomiej Bukowski- (void)_rebuildBridge 300b0185235SEric Samelson{ 301b0185235SEric Samelson if (!self.isBridgeAlreadyLoading) { 302b0185235SEric Samelson self.isBridgeAlreadyLoading = YES; 303b0185235SEric Samelson dispatch_async(dispatch_get_main_queue(), ^{ 304b9d8b772STomasz Sapeta [self _overrideUserInterfaceStyleOf:self]; 30583bf929eSBartłomiej Bukowski [self _overrideAppearanceModuleBehaviour]; 30627700081SBartłomiej Bukowski [self _invalidateRecoveryTimer]; 30727700081SBartłomiej Bukowski [self.appRecord.appManager rebuildBridge]; 308b0185235SEric Samelson }); 309b0185235SEric Samelson } 310b0185235SEric Samelson} 311b0185235SEric Samelson 312ce63205bSTomasz Sapeta- (void)foregroundControllers 313ce63205bSTomasz Sapeta{ 314ce63205bSTomasz Sapeta if (_backgroundedControllers != nil) { 315ce63205bSTomasz Sapeta __block UIViewController *parentController = self; 316ce63205bSTomasz Sapeta 317ce63205bSTomasz Sapeta [_backgroundedControllers enumerateObjectsUsingBlock:^(UIViewController * _Nonnull viewController, NSUInteger idx, BOOL * _Nonnull stop) { 318ce63205bSTomasz Sapeta [parentController presentViewController:viewController animated:NO completion:nil]; 319ce63205bSTomasz Sapeta parentController = viewController; 320ce63205bSTomasz Sapeta }]; 321ce63205bSTomasz Sapeta 322ce63205bSTomasz Sapeta _backgroundedControllers = nil; 323ce63205bSTomasz Sapeta } 324ce63205bSTomasz Sapeta} 325ce63205bSTomasz Sapeta 326ce63205bSTomasz Sapeta- (void)backgroundControllers 327ce63205bSTomasz Sapeta{ 328ce63205bSTomasz Sapeta UIViewController *childController = [self presentedViewController]; 329ce63205bSTomasz Sapeta 330ce63205bSTomasz Sapeta if (childController != nil) { 331ce63205bSTomasz Sapeta if (_backgroundedControllers == nil) { 332ce63205bSTomasz Sapeta _backgroundedControllers = [NSMutableArray new]; 333ce63205bSTomasz Sapeta } 334ce63205bSTomasz Sapeta 335ce63205bSTomasz Sapeta while (childController != nil) { 336ce63205bSTomasz Sapeta [_backgroundedControllers addObject:childController]; 337ce63205bSTomasz Sapeta childController = childController.presentedViewController; 338ce63205bSTomasz Sapeta } 339ce63205bSTomasz Sapeta } 340ce63205bSTomasz Sapeta} 341ce63205bSTomasz Sapeta 34227700081SBartłomiej Bukowski/** 34327700081SBartłomiej Bukowski * In managed app we expect two kinds of manifest: 34427700081SBartłomiej Bukowski * - optimistic one (served from cache) 34527700081SBartłomiej Bukowski * - actual one served when app is fetched. 34627700081SBartłomiej Bukowski * For each of them we should show SplashScreen, 34727700081SBartłomiej Bukowski * therefore for any consecutive SplashScreen.show call we just reconfigure what's already visible. 3484d7a9b36SBartłomiej Bukowski * In HomeApp or standalone apps this function is no-op as SplashScreen is managed differently. 34927700081SBartłomiej Bukowski */ 35050661f5cSWill Schurman- (void)_showOrReconfigureManagedAppSplashScreen:(EXManifestsManifest *)manifest 35127700081SBartłomiej Bukowski{ 35227700081SBartłomiej Bukowski if (_isStandalone || _isHomeApp) { 35327700081SBartłomiej Bukowski return; 35427700081SBartłomiej Bukowski } 35527700081SBartłomiej Bukowski if (!_managedAppSplashScreenViewProvider) { 35627700081SBartłomiej Bukowski _managedAppSplashScreenViewProvider = [[EXManagedAppSplashScreenViewProvider alloc] initWithManifest:manifest]; 35727700081SBartłomiej Bukowski 3589fcddb20Sandy [self _showManagedSplashScreenWithProvider:_managedAppSplashScreenViewProvider]; 35927700081SBartłomiej Bukowski } else { 36027700081SBartłomiej Bukowski [_managedAppSplashScreenViewProvider updateSplashScreenViewWithManifest:manifest]; 36127700081SBartłomiej Bukowski } 36227700081SBartłomiej Bukowski} 36327700081SBartłomiej Bukowski 364dfa8cf86SEric Samelson- (void)_showCachedExperienceAlert 365dfa8cf86SEric Samelson{ 366dfa8cf86SEric Samelson if (self.isStandalone || self.isHomeApp) { 367dfa8cf86SEric Samelson return; 368dfa8cf86SEric Samelson } 369dfa8cf86SEric Samelson 370dfa8cf86SEric Samelson dispatch_async(dispatch_get_main_queue(), ^{ 371dfa8cf86SEric Samelson UIAlertController *alert = [UIAlertController 372dfa8cf86SEric Samelson alertControllerWithTitle:@"Using a cached project" 373dfa8cf86SEric Samelson message:@"If you did not intend to use a cached project, check your network connection and reload." 374dfa8cf86SEric Samelson preferredStyle:UIAlertControllerStyleAlert]; 37557bba33fSEric Samelson [alert addAction:[UIAlertAction actionWithTitle:@"Reload" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 37657bba33fSEric Samelson [self refresh]; 37757bba33fSEric Samelson }]]; 37857bba33fSEric Samelson [alert addAction:[UIAlertAction actionWithTitle:@"Use cache" style:UIAlertActionStyleCancel handler:nil]]; 379dfa8cf86SEric Samelson [self presentViewController:alert animated:YES completion:nil]; 380dfa8cf86SEric Samelson }); 381dfa8cf86SEric Samelson} 382dfa8cf86SEric Samelson 3837c3ed0c0SDouglas Lowder- (void)_setLoadingViewStatusIfEnabledFromAppLoader:(EXAbstractLoader *)appLoader 38457bba33fSEric Samelson{ 38557bba33fSEric Samelson if (appLoader.shouldShowRemoteUpdateStatus) { 38657bba33fSEric Samelson [self.appLoadingProgressWindowController updateStatus:appLoader.remoteUpdateStatus]; 38757bba33fSEric Samelson } else { 38857bba33fSEric Samelson [self.appLoadingProgressWindowController hide]; 38957bba33fSEric Samelson } 39057bba33fSEric Samelson} 39157bba33fSEric Samelson 392cdf87a03SStanisław Chmiela- (void)_showSplashScreenWithProvider:(id<EXSplashScreenViewProvider>)provider 393cdf87a03SStanisław Chmiela{ 394ea3f1d02STomasz Sapeta EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[EXModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]]; 395cdf87a03SStanisław Chmiela 396cdf87a03SStanisław Chmiela // EXSplashScreenService presents a splash screen on a root view controller 397cdf87a03SStanisław Chmiela // at the start of the app. Since we want the EXAppViewController to manage 398cdf87a03SStanisław Chmiela // the lifecycle of the splash screen we need to: 399cdf87a03SStanisław Chmiela // 1. present the splash screen on EXAppViewController 400cdf87a03SStanisław Chmiela // 2. hide the splash screen of root view controller 40123fc6879SBartłomiej Bukowski // Disclaimer: 40223fc6879SBartłomiej Bukowski // there's only one root view controller, but possibly many EXAppViewControllers 403dc898499SJames Ide // (in Expo Go: one project -> one EXAppViewController) 40423fc6879SBartłomiej Bukowski // and we want to hide SplashScreen only once for the root view controller, hence the "once" 40523fc6879SBartłomiej Bukowski static dispatch_once_t once; 406cdf87a03SStanisław Chmiela void (^hideRootViewControllerSplashScreen)(void) = ^void() { 40723fc6879SBartłomiej Bukowski dispatch_once(&once, ^{ 408cdf87a03SStanisław Chmiela UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController; 409cdf87a03SStanisław Chmiela [splashScreenService hideSplashScreenFor:rootViewController 4103ef102f0SKudo Chien options:EXSplashScreenDefault 411cdf87a03SStanisław Chmiela successCallback:^(BOOL hasEffect){} 412cdf87a03SStanisław Chmiela failureCallback:^(NSString * _Nonnull message) { 413efd75decSTomasz Sapeta EXLogWarn(@"Hiding splash screen from root view controller did not succeed: %@", message); 414cdf87a03SStanisław Chmiela }]; 41523fc6879SBartłomiej Bukowski }); 416cdf87a03SStanisław Chmiela }; 417cdf87a03SStanisław Chmiela 418efd75decSTomasz Sapeta EX_WEAKIFY(self); 419cdf87a03SStanisław Chmiela dispatch_async(dispatch_get_main_queue(), ^{ 420efd75decSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 421cdf87a03SStanisław Chmiela [splashScreenService showSplashScreenFor:self 4223ef102f0SKudo Chien options:EXSplashScreenDefault 423cdf87a03SStanisław Chmiela splashScreenViewProvider:provider 424cdf87a03SStanisław Chmiela successCallback:hideRootViewControllerSplashScreen 425efd75decSTomasz Sapeta failureCallback:^(NSString *message){ EXLogWarn(@"%@", message); }]; 426cdf87a03SStanisław Chmiela }); 427cdf87a03SStanisław Chmiela} 428cdf87a03SStanisław Chmiela 4299fcddb20Sandy- (void)_showManagedSplashScreenWithProvider:(id<EXSplashScreenViewProvider>)provider 4309fcddb20Sandy{ 4319fcddb20Sandy 4329fcddb20Sandy EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[EXModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]]; 4339fcddb20Sandy 434efd75decSTomasz Sapeta EX_WEAKIFY(self); 4359fcddb20Sandy dispatch_async(dispatch_get_main_queue(), ^{ 436efd75decSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 4379fcddb20Sandy 4389fcddb20Sandy UIView *rootView = self.view; 4399fcddb20Sandy UIView *splashScreenView = [provider createSplashScreenView]; 4403c604617Sandy self.managedSplashScreenController = [[EXManagedAppSplashScreenViewController alloc] initWithRootView:rootView 4419fcddb20Sandy splashScreenView:splashScreenView]; 4429fcddb20Sandy [splashScreenService showSplashScreenFor:self 4433ef102f0SKudo Chien options:EXSplashScreenDefault 4443c604617Sandy splashScreenController:self.managedSplashScreenController 4459fcddb20Sandy successCallback:^{} 446efd75decSTomasz Sapeta failureCallback:^(NSString *message){ EXLogWarn(@"%@", message); }]; 4479fcddb20Sandy }); 4489fcddb20Sandy 4499fcddb20Sandy} 4509fcddb20Sandy 45195dd8db8SBartłomiej Bukowski- (void)hideLoadingProgressWindow 45295dd8db8SBartłomiej Bukowski{ 45395dd8db8SBartłomiej Bukowski [self.appLoadingProgressWindowController hide]; 4543c604617Sandy if (self.managedSplashScreenController) { 4553c604617Sandy [self.managedSplashScreenController startSplashScreenVisibleTimer]; 4563c604617Sandy } 45795dd8db8SBartłomiej Bukowski} 45895dd8db8SBartłomiej Bukowski 4599088dd0eSBen Roth#pragma mark - EXAppLoaderDelegate 4603217e243SBen Roth 4617c3ed0c0SDouglas Lowder- (void)appLoader:(EXAbstractLoader *)appLoader didLoadOptimisticManifest:(EXManifestsManifest *)manifest 4623217e243SBen Roth{ 46327700081SBartłomiej Bukowski if (_appLoadingCancelView) { 464efd75decSTomasz Sapeta EX_WEAKIFY(self); 4654d7a9b36SBartłomiej Bukowski dispatch_async(dispatch_get_main_queue(), ^{ 466efd75decSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 4674d7a9b36SBartłomiej Bukowski [self.appLoadingCancelView removeFromSuperview]; 4684d7a9b36SBartłomiej Bukowski self.appLoadingCancelView = nil; 4694d7a9b36SBartłomiej Bukowski }); 47027700081SBartłomiej Bukowski } 47127700081SBartłomiej Bukowski [self _showOrReconfigureManagedAppSplashScreen:manifest]; 47257bba33fSEric Samelson [self _setLoadingViewStatusIfEnabledFromAppLoader:appLoader]; 473f44e163eSBen Roth if ([EXKernel sharedInstance].browserController) { 474f44e163eSBen Roth [[EXKernel sharedInstance].browserController addHistoryItemWithUrl:appLoader.manifestUrl manifest:manifest]; 475f44e163eSBen Roth } 47627700081SBartłomiej Bukowski [self _rebuildBridge]; 4773217e243SBen Roth} 4783217e243SBen Roth 4797c3ed0c0SDouglas Lowder- (void)appLoader:(EXAbstractLoader *)appLoader didLoadBundleWithProgress:(EXLoadingProgress *)progress 4803217e243SBen Roth{ 4816330114fSEric Samelson if (self->_appRecord.appManager.status != kEXReactAppManagerStatusRunning) { 4829ef743bcSBartłomiej Bukowski [self.appLoadingProgressWindowController updateStatusWithProgress:progress]; 4833217e243SBen Roth } 4846330114fSEric Samelson} 4853217e243SBen Roth 4867c3ed0c0SDouglas Lowder- (void)appLoader:(EXAbstractLoader *)appLoader didFinishLoadingManifest:(EXManifestsManifest *)manifest bundle:(NSData *)data 4873217e243SBen Roth{ 48827700081SBartłomiej Bukowski [self _showOrReconfigureManagedAppSplashScreen:manifest]; 48912075537Saleqsio if (!_isHomeApp) { 49012075537Saleqsio [EXTextDirectionController setSupportsRTL:[self _readSupportsRTLFromManifest:_appRecord.appLoader.manifest]]; 49112075537Saleqsio } 49227700081SBartłomiej Bukowski [self _rebuildBridge]; 493d718b4b8SBen Roth if (self->_appRecord.appManager.status == kEXReactAppManagerStatusBridgeLoading) { 494d718b4b8SBen Roth [self->_appRecord.appManager appLoaderFinished]; 4953217e243SBen Roth } 496dfa8cf86SEric Samelson 49757bba33fSEric Samelson if (!appLoader.isUpToDate && appLoader.shouldShowRemoteUpdateStatus) { 498dfa8cf86SEric Samelson [self _showCachedExperienceAlert]; 499dfa8cf86SEric Samelson } 5003217e243SBen Roth} 5013217e243SBen Roth 5027c3ed0c0SDouglas Lowder- (void)appLoader:(EXAbstractLoader *)appLoader didFailWithError:(NSError *)error 5033217e243SBen Roth{ 5043217e243SBen Roth if (_appRecord.appManager.status == kEXReactAppManagerStatusBridgeLoading) { 5053217e243SBen Roth [_appRecord.appManager appLoaderFailedWithError:error]; 5063217e243SBen Roth } 5073217e243SBen Roth [self maybeShowError:error]; 5083217e243SBen Roth} 5093217e243SBen Roth 5107c3ed0c0SDouglas Lowder- (void)appLoader:(EXAbstractLoader *)appLoader didResolveUpdatedBundleWithManifest:(EXManifestsManifest * _Nullable)manifest isFromCache:(BOOL)isFromCache error:(NSError * _Nullable)error 51130fccd70SEric Samelson{ 51230fccd70SEric Samelson [[EXKernel sharedInstance].serviceRegistry.updatesManager notifyApp:_appRecord ofDownloadWithManifest:manifest isNew:!isFromCache error:error]; 51330fccd70SEric Samelson} 51430fccd70SEric Samelson 5153217e243SBen Roth#pragma mark - EXReactAppManagerDelegate 5163217e243SBen Roth 5173217e243SBen Roth- (void)reactAppManagerIsReadyForLoad:(EXReactAppManager *)appManager 5183217e243SBen Roth{ 5193217e243SBen Roth UIView *reactView = appManager.rootView; 520edc8619eSBen Roth reactView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); 5213217e243SBen Roth reactView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 5221f6f211eSEvan Bacon // Set this view to transparent so the root view background color aligns with custom development clients where the 5231f6f211eSEvan Bacon // background color is the view controller root view. 5241f6f211eSEvan Bacon reactView.backgroundColor = [UIColor clearColor]; 5253217e243SBen Roth 5263217e243SBen Roth [_contentView removeFromSuperview]; 5273217e243SBen Roth _contentView = reactView; 5283217e243SBen Roth [self.view addSubview:_contentView]; 5293217e243SBen Roth [self.view sendSubviewToBack:_contentView]; 5303217e243SBen Roth [reactView becomeFirstResponder]; 53156fb41fdSBrent Vatne 53256fb41fdSBrent Vatne // Set root view background color after adding as subview so we can access window 5331f6f211eSEvan Bacon [self _setBackgroundColor]; 5343217e243SBen Roth} 5353217e243SBen Roth 5363217e243SBen Roth- (void)reactAppManagerStartedLoadingJavaScript:(EXReactAppManager *)appManager 5373217e243SBen Roth{ 5383217e243SBen Roth EXAssertMainThread(); 5393217e243SBen Roth self.isLoading = YES; 5403217e243SBen Roth} 5413217e243SBen Roth 5423217e243SBen Roth- (void)reactAppManagerFinishedLoadingJavaScript:(EXReactAppManager *)appManager 5433217e243SBen Roth{ 5443217e243SBen Roth EXAssertMainThread(); 54566169681SBen Roth self.isLoading = NO; 54666169681SBen Roth if ([EXKernel sharedInstance].browserController) { 54766169681SBen Roth [[EXKernel sharedInstance].browserController appDidFinishLoadingSuccessfully:_appRecord]; 5483217e243SBen Roth } 5493217e243SBen Roth} 5503217e243SBen Roth 55127700081SBartłomiej Bukowski- (void)reactAppManagerAppContentDidAppear:(EXReactAppManager *)appManager 55227700081SBartłomiej Bukowski{ 553ea3f1d02STomasz Sapeta EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[EXModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]]; 55427700081SBartłomiej Bukowski [splashScreenService onAppContentDidAppear:self]; 55527700081SBartłomiej Bukowski} 55627700081SBartłomiej Bukowski 55727700081SBartłomiej Bukowski- (void)reactAppManagerAppContentWillReload:(EXReactAppManager *)appManager { 558ea3f1d02STomasz Sapeta EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[EXModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]]; 55927700081SBartłomiej Bukowski [splashScreenService onAppContentWillReload:self]; 56027700081SBartłomiej Bukowski} 56127700081SBartłomiej Bukowski 5623217e243SBen Roth- (void)reactAppManager:(EXReactAppManager *)appManager failedToLoadJavaScriptWithError:(NSError *)error 5633217e243SBen Roth{ 5643217e243SBen Roth EXAssertMainThread(); 5653217e243SBen Roth [self maybeShowError:error]; 5663217e243SBen Roth} 5673217e243SBen Roth 5683217e243SBen Roth- (void)reactAppManagerDidInvalidate:(EXReactAppManager *)appManager 5693217e243SBen Roth{ 5703217e243SBen Roth} 5713217e243SBen Roth 5723217e243SBen Roth- (void)errorViewDidSelectRetry:(EXErrorView *)errorView 5733217e243SBen Roth{ 5743217e243SBen Roth [self refresh]; 5753217e243SBen Roth} 5763217e243SBen Roth 5770b761b65SWojciech Dróżdż// In Expo Go the ScreenOrientationViewController.swift is not used, therefore it is necessary to write the same 5780b761b65SWojciech Dróżdż// functionality into the EXAppViewController 5793217e243SBen Roth#pragma mark - orientation 5803217e243SBen Roth 5813217e243SBen Roth- (UIInterfaceOrientationMask)supportedInterfaceOrientations 5823217e243SBen Roth{ 5838b74a44fSŁukasz Kosmaty if ([self shouldUseRNScreenOrientation]) { 5848b74a44fSŁukasz Kosmaty return [super supportedInterfaceOrientations]; 5858b74a44fSŁukasz Kosmaty } 5868b74a44fSŁukasz Kosmaty 587a859e3aeSWojciech Dróżdż if ([ScreenOrientationRegistry.shared requiredOrientationMask] > 0 && !self.isHomeApp) { 5880b761b65SWojciech Dróżdż return [ScreenOrientationRegistry.shared requiredOrientationMask]; 589ee295080SŁukasz Kosmaty } 5900b761b65SWojciech Dróżdż 591ee295080SŁukasz Kosmaty return [self orientationMaskFromManifestOrDefault]; 592ee295080SŁukasz Kosmaty} 593ee295080SŁukasz Kosmaty 5948b74a44fSŁukasz Kosmaty- (BOOL)shouldUseRNScreenOrientation 5958b74a44fSŁukasz Kosmaty{ 5968b74a44fSŁukasz Kosmaty Class screenWindowTraitsClass = [self->_appRecord.appManager versionedClassFromString:@"RNSScreenWindowTraits"]; 5978b74a44fSŁukasz Kosmaty if ([screenWindowTraitsClass respondsToSelector:@selector(shouldAskScreensForScreenOrientationInViewController:)]) { 5988b74a44fSŁukasz Kosmaty id<EXKernelRNSScreenWindowTraits> screenWindowTraits = (id<EXKernelRNSScreenWindowTraits>)screenWindowTraitsClass; 5998b74a44fSŁukasz Kosmaty return [screenWindowTraits shouldAskScreensForScreenOrientationInViewController:self]; 6008b74a44fSŁukasz Kosmaty } 6018b74a44fSŁukasz Kosmaty return NO; 6028b74a44fSŁukasz Kosmaty} 6038b74a44fSŁukasz Kosmaty 604*24857eeaSWojciech Dróżdż- (UIInterfaceOrientationMask)orientationMaskFromManifestOrDefault 605*24857eeaSWojciech Dróżdż{ 6063217e243SBen Roth if (_appRecord.appLoader.manifest) { 607c8841cb0SWill Schurman NSString *orientationConfig = _appRecord.appLoader.manifest.orientation; 6083217e243SBen Roth if ([orientationConfig isEqualToString:@"portrait"]) { 6093217e243SBen Roth // lock to portrait 6103217e243SBen Roth return UIInterfaceOrientationMaskPortrait; 6113217e243SBen Roth } else if ([orientationConfig isEqualToString:@"landscape"]) { 6123217e243SBen Roth // lock to landscape 6133217e243SBen Roth return UIInterfaceOrientationMaskLandscape; 6143217e243SBen Roth } 6153217e243SBen Roth } 6163217e243SBen Roth // no config or default value: allow autorotation 6173217e243SBen Roth return UIInterfaceOrientationMaskAllButUpsideDown; 6183217e243SBen Roth} 6193217e243SBen Roth 620*24857eeaSWojciech Dróżdż- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator 621*24857eeaSWojciech Dróżdż{ 622*24857eeaSWojciech Dróżdż [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 623*24857eeaSWojciech Dróżdż 624*24857eeaSWojciech Dróżdż __weak typeof(self) weakSelf = self; 625*24857eeaSWojciech Dróżdż 626*24857eeaSWojciech Dróżdż // Update after the transition ends, this ensures that the trait collection passed to didUpdateDimensionsEvent is already updated 627*24857eeaSWojciech Dróżdż [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { 628*24857eeaSWojciech Dróżdż __strong __typeof(self) strongSelf = weakSelf; 629*24857eeaSWojciech Dróżdż 630*24857eeaSWojciech Dróżdż if (!strongSelf) { 631*24857eeaSWojciech Dróżdż return; 632*24857eeaSWojciech Dróżdż } 633*24857eeaSWojciech Dróżdż 634*24857eeaSWojciech Dróżdż if (self.windowInterfaceOrientation != self.previousInterfaceOrientation) { 635*24857eeaSWojciech Dróżdż [ScreenOrientationRegistry.shared viewDidTransitionToOrientation:self.windowInterfaceOrientation]; 636*24857eeaSWojciech Dróżdż } 637*24857eeaSWojciech Dróżdż 638*24857eeaSWojciech Dróżdż self->_previousInterfaceOrientation = self.windowInterfaceOrientation; 639*24857eeaSWojciech Dróżdż } completion: nil]; 640*24857eeaSWojciech Dróżdż} 641*24857eeaSWojciech Dróżdż 6423da4a97bSQuinlan Jung- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection { 6433da4a97bSQuinlan Jung [super traitCollectionDidChange:previousTraitCollection]; 6443da4a97bSQuinlan Jung if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass) 6453da4a97bSQuinlan Jung || (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass)) { 646ee295080SŁukasz Kosmaty 6470b761b65SWojciech Dróżdż [ScreenOrientationRegistry.shared traitCollectionDidChangeTo:self.traitCollection]; 6483da4a97bSQuinlan Jung } 6493da4a97bSQuinlan Jung} 6503da4a97bSQuinlan Jung 65183bf929eSBartłomiej Bukowski#pragma mark - RCTAppearanceModule 65283bf929eSBartłomiej Bukowski 65383bf929eSBartłomiej Bukowski/** 65483bf929eSBartłomiej Bukowski * This function overrides behaviour of RCTAppearanceModule 65583bf929eSBartłomiej Bukowski * basing on 'userInterfaceStyle' option from the app manifest. 65683bf929eSBartłomiej Bukowski * It also defaults the RCTAppearanceModule to 'light'. 65783bf929eSBartłomiej Bukowski */ 65883bf929eSBartłomiej Bukowski- (void)_overrideAppearanceModuleBehaviour 65983bf929eSBartłomiej Bukowski{ 66083bf929eSBartłomiej Bukowski NSString *userInterfaceStyle = [self _readUserInterfaceStyleFromManifest:_appRecord.appLoader.manifest]; 66183bf929eSBartłomiej Bukowski NSString *appearancePreference = nil; 6629f094991SJuwan Wheatley if ([userInterfaceStyle isEqualToString:@"light"]) { 66383bf929eSBartłomiej Bukowski appearancePreference = @"light"; 66483bf929eSBartłomiej Bukowski } else if ([userInterfaceStyle isEqualToString:@"dark"]) { 66583bf929eSBartłomiej Bukowski appearancePreference = @"dark"; 66683bf929eSBartłomiej Bukowski } else if ([userInterfaceStyle isEqualToString:@"automatic"]) { 66783bf929eSBartłomiej Bukowski appearancePreference = nil; 66883bf929eSBartłomiej Bukowski } 66983bf929eSBartłomiej Bukowski RCTOverrideAppearancePreference(appearancePreference); 670af2ec015STomasz Sapeta#if defined(INCLUDES_VERSIONED_CODE) && __has_include(<ABI49_0_0React/ABI49_0_0RCTAppearance.h>) 671af2ec015STomasz Sapeta ABI49_0_0RCTOverrideAppearancePreference(appearancePreference); 672af2ec015STomasz Sapeta#endif 673fe5cfb17STomasz Sapeta#if defined(INCLUDES_VERSIONED_CODE) && __has_include(<ABI48_0_0React/ABI48_0_0RCTAppearance.h>) 674fe5cfb17STomasz Sapeta ABI48_0_0RCTOverrideAppearancePreference(appearancePreference); 675fe5cfb17STomasz Sapeta#endif 676753557f6STomasz Sapeta#if defined(INCLUDES_VERSIONED_CODE) && __has_include(<ABI47_0_0React/ABI47_0_0RCTAppearance.h>) 677753557f6STomasz Sapeta ABI47_0_0RCTOverrideAppearancePreference(appearancePreference); 678753557f6STomasz Sapeta#endif 67915c3ab77Sandy 68083bf929eSBartłomiej Bukowski} 68183bf929eSBartłomiej Bukowski 6824ea6e0faSTomasz Sapeta#pragma mark - user interface style 6834ea6e0faSTomasz Sapeta 684b9d8b772STomasz Sapeta- (void)_overrideUserInterfaceStyleOf:(UIViewController *)viewController 6854ea6e0faSTomasz Sapeta{ 6864ea6e0faSTomasz Sapeta if (@available(iOS 13.0, *)) { 68713825f6cSBartłomiej Bukowski NSString *userInterfaceStyle = [self _readUserInterfaceStyleFromManifest:_appRecord.appLoader.manifest]; 688b9d8b772STomasz Sapeta viewController.overrideUserInterfaceStyle = [self _userInterfaceStyleForString:userInterfaceStyle]; 6894ea6e0faSTomasz Sapeta } 6904ea6e0faSTomasz Sapeta} 6914ea6e0faSTomasz Sapeta 69250661f5cSWill Schurman- (NSString * _Nullable)_readUserInterfaceStyleFromManifest:(EXManifestsManifest *)manifest 69313825f6cSBartłomiej Bukowski{ 694c8841cb0SWill Schurman return manifest.userInterfaceStyle; 69513825f6cSBartłomiej Bukowski} 69613825f6cSBartłomiej Bukowski 6974ea6e0faSTomasz Sapeta- (UIUserInterfaceStyle)_userInterfaceStyleForString:(NSString *)userInterfaceStyleString API_AVAILABLE(ios(12.0)) { 6984ea6e0faSTomasz Sapeta if ([userInterfaceStyleString isEqualToString:@"dark"]) { 6994ea6e0faSTomasz Sapeta return UIUserInterfaceStyleDark; 7004ea6e0faSTomasz Sapeta } 7014ea6e0faSTomasz Sapeta if ([userInterfaceStyleString isEqualToString:@"automatic"]) { 7024ea6e0faSTomasz Sapeta return UIUserInterfaceStyleUnspecified; 7034ea6e0faSTomasz Sapeta } 7049f094991SJuwan Wheatley if ([userInterfaceStyleString isEqualToString:@"light"]) { 7054ea6e0faSTomasz Sapeta return UIUserInterfaceStyleLight; 7064ea6e0faSTomasz Sapeta } 7074ea6e0faSTomasz Sapeta 7089f094991SJuwan Wheatley return UIUserInterfaceStyleUnspecified; 7099f094991SJuwan Wheatley} 7109f094991SJuwan Wheatley 71156fb41fdSBrent Vatne#pragma mark - root view and window background color 7123691b080SBrent Vatne 7131f6f211eSEvan Bacon- (void)_setBackgroundColor 7143691b080SBrent Vatne{ 7153691b080SBrent Vatne NSString *backgroundColorString = [self _readBackgroundColorFromManifest:_appRecord.appLoader.manifest]; 7163691b080SBrent Vatne UIColor *backgroundColor = [EXUtil colorWithHexString:backgroundColorString]; 7171f6f211eSEvan Bacon self.view.backgroundColor = [UIColor clearColor]; 7183691b080SBrent Vatne 7191f6f211eSEvan Bacon // NOTE(evanbacon): `self.view.window.rootViewController.view` represents the top-most window's root view controller's view which is the same 7201f6f211eSEvan Bacon // view we set in `expo-system-ui`'s `setBackgroundColorAsync` method. 7213691b080SBrent Vatne if (backgroundColor) { 7221f6f211eSEvan Bacon if (self.view.window.rootViewController != nil && self.view.window.rootViewController.view != nil) { 7231f6f211eSEvan Bacon self.view.window.rootViewController.view.backgroundColor = backgroundColor; 7241f6f211eSEvan Bacon } 7251f6f211eSEvan Bacon self.view.window.backgroundColor = backgroundColor; 7263691b080SBrent Vatne } else { 7271f6f211eSEvan Bacon // Reset this color to white so splash and other screens don't load against a black background. 7281f6f211eSEvan Bacon if (self.view.window.rootViewController != nil && self.view.window.rootViewController.view != nil) { 7291f6f211eSEvan Bacon self.view.window.rootViewController.view.backgroundColor = [UIColor whiteColor]; 7301f6f211eSEvan Bacon } 73156fb41fdSBrent Vatne // NOTE(brentvatne): we used to use white as a default background color for window but this caused 73256fb41fdSBrent Vatne // problems when using form sheet presentation style with vcs eg: <Modal /> and native-stack. Most 73356fb41fdSBrent Vatne // users expect the background behind these to be black, which is the default if backgroundColor is nil. 7341f6f211eSEvan Bacon self.view.window.backgroundColor = nil; 73556fb41fdSBrent Vatne 7363691b080SBrent Vatne // NOTE(brentvatne): we may want to default to respecting the default system background color 7373691b080SBrent Vatne // on iOS13 and higher, but if we do make this choice then we will have to implement it on Android 7381f6f211eSEvan Bacon // as well. This would also be a breaking change. Leaving this here as a placeholder for the future. 7393691b080SBrent Vatne // if (@available(iOS 13.0, *)) { 7401f6f211eSEvan Bacon // self.view.backgroundColor = [UIColor systemBackgroundColor]; 7413691b080SBrent Vatne // } else { 7421f6f211eSEvan Bacon // self.view.backgroundColor = [UIColor whiteColor]; 7433691b080SBrent Vatne // } 7443691b080SBrent Vatne } 7453691b080SBrent Vatne} 7463691b080SBrent Vatne 74750661f5cSWill Schurman- (NSString * _Nullable)_readBackgroundColorFromManifest:(EXManifestsManifest *)manifest 7483691b080SBrent Vatne{ 7494d38de7eSWill Schurman return manifest.iosOrRootBackgroundColor; 7503691b080SBrent Vatne} 7513691b080SBrent Vatne 752*24857eeaSWojciech Dróżdż- (UIInterfaceOrientation)windowInterfaceOrientation { 753*24857eeaSWojciech Dróżdż return [[[UIApplication sharedApplication].windows firstObject].windowScene interfaceOrientation]; 754*24857eeaSWojciech Dróżdż} 7553691b080SBrent Vatne 7563217e243SBen Roth#pragma mark - Internal 7573217e243SBen Roth 7583217e243SBen Roth- (void)_showErrorWithType:(EXFatalErrorType)type error:(nullable NSError *)error 7593217e243SBen Roth{ 7603217e243SBen Roth EXAssertMainThread(); 7614f9de556SBen Roth _dtmLastFatalErrorShown = [NSDate date]; 7623217e243SBen Roth if (_errorView && _contentView == _errorView) { 7633217e243SBen Roth // already showing, just update 7643217e243SBen Roth _errorView.type = type; 7653217e243SBen Roth _errorView.error = error; 7663217e243SBen Roth } { 7673217e243SBen Roth [_contentView removeFromSuperview]; 7683217e243SBen Roth if (!_errorView) { 769edc8619eSBen Roth _errorView = [[EXErrorView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)]; 7703217e243SBen Roth _errorView.delegate = self; 771d2210950SBen Roth _errorView.appRecord = _appRecord; 7723217e243SBen Roth } 7733217e243SBen Roth _errorView.type = type; 7743217e243SBen Roth _errorView.error = error; 7753217e243SBen Roth _contentView = _errorView; 7763217e243SBen Roth [self.view addSubview:_contentView]; 7773217e243SBen Roth } 7783217e243SBen Roth} 7793217e243SBen Roth 7803217e243SBen Roth- (void)setIsLoading:(BOOL)isLoading 7813217e243SBen Roth{ 7824f9de556SBen Roth if ([_appRecord.appManager enablesDeveloperTools] && _dtmLastFatalErrorShown) { 7834f9de556SBen Roth if ([_dtmLastFatalErrorShown timeIntervalSinceNow] >= -kEXDevelopmentErrorCoolDownSeconds) { 7844f9de556SBen Roth // we just showed a fatal error very recently, do not begin loading. 7854f9de556SBen Roth // this can happen in some cases where react native sends the 'started loading' notif 7864f9de556SBen Roth // in spite of a packager error. 7874f9de556SBen Roth return; 7884f9de556SBen Roth } 7894f9de556SBen Roth } 7903217e243SBen Roth _isLoading = isLoading; 791efd75decSTomasz Sapeta EX_WEAKIFY(self); 7923217e243SBen Roth dispatch_async(dispatch_get_main_queue(), ^{ 793efd75decSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 7944d7a9b36SBartłomiej Bukowski if (!isLoading) { 7959ef743bcSBartłomiej Bukowski [self.appLoadingProgressWindowController hide]; 7963217e243SBen Roth } 7973217e243SBen Roth }); 7983217e243SBen Roth} 7993217e243SBen Roth 800e7ac6e4fSBen Roth#pragma mark - error recovery 801e7ac6e4fSBen Roth 802e7ac6e4fSBen Roth- (BOOL)_willAutoRecoverFromError:(NSError *)error 803e7ac6e4fSBen Roth{ 804e7ac6e4fSBen Roth if (![_appRecord.appManager enablesDeveloperTools]) { 805167fd314SWill Schurman BOOL shouldRecover = [[EXKernel sharedInstance].serviceRegistry.errorRecoveryManager experienceShouldReloadOnError:_appRecord.scopeKey]; 806e7ac6e4fSBen Roth if (shouldRecover) { 807e7ac6e4fSBen Roth [self _invalidateRecoveryTimer]; 808e7ac6e4fSBen Roth _tmrAutoReloadDebounce = [NSTimer scheduledTimerWithTimeInterval:kEXAutoReloadDebounceSeconds 809e7ac6e4fSBen Roth target:self 810e7ac6e4fSBen Roth selector:@selector(refresh) 811e7ac6e4fSBen Roth userInfo:nil 812e7ac6e4fSBen Roth repeats:NO]; 813e7ac6e4fSBen Roth } 814e7ac6e4fSBen Roth return shouldRecover; 815e7ac6e4fSBen Roth } 816e7ac6e4fSBen Roth return NO; 817e7ac6e4fSBen Roth} 818e7ac6e4fSBen Roth 819e7ac6e4fSBen Roth- (void)_invalidateRecoveryTimer 820e7ac6e4fSBen Roth{ 821e7ac6e4fSBen Roth if (_tmrAutoReloadDebounce) { 822e7ac6e4fSBen Roth [_tmrAutoReloadDebounce invalidate]; 823e7ac6e4fSBen Roth _tmrAutoReloadDebounce = nil; 824e7ac6e4fSBen Roth } 825e7ac6e4fSBen Roth} 826e7ac6e4fSBen Roth 82727700081SBartłomiej Bukowski#pragma mark - EXAppLoadingCancelViewDelegate 82827700081SBartłomiej Bukowski 82927700081SBartłomiej Bukowski- (void)appLoadingCancelViewDidCancel:(EXAppLoadingCancelView *)view { 83027700081SBartłomiej Bukowski if ([EXKernel sharedInstance].browserController) { 83127700081SBartłomiej Bukowski [[EXKernel sharedInstance].browserController moveHomeToVisible]; 83227700081SBartłomiej Bukowski } 83327700081SBartłomiej Bukowski} 83427700081SBartłomiej Bukowski 8353217e243SBen Roth@end 8363217e243SBen Roth 8373217e243SBen RothNS_ASSUME_NONNULL_END 838