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