17f6367d0SBen Roth// Copyright 2015-present 650 Industries. All rights reserved. 27f6367d0SBen Roth 37f6367d0SBen Roth@import UIKit; 47f6367d0SBen Roth 5c8452ff7SBartłomiej Bukowski#import <ExpoModulesCore/EXDefines.h> 6c8452ff7SBartłomiej Bukowski 77f6367d0SBen Roth#import "EXAppViewController.h" 87f6367d0SBen Roth#import "EXHomeAppManager.h" 97f6367d0SBen Roth#import "EXKernel.h" 10*19a0af8dSWill Schurman#import "EXDevelopmentHomeLoader.h" 117f6367d0SBen Roth#import "EXKernelAppRecord.h" 127f6367d0SBen Roth#import "EXKernelAppRegistry.h" 13f322f621SBen Roth#import "EXKernelLinkingManager.h" 147f6367d0SBen Roth#import "EXKernelServiceRegistry.h" 157f6367d0SBen Roth#import "EXRootViewController.h" 16f67462bcSTomasz Sapeta#import "EXDevMenuManager.h" 17*19a0af8dSWill Schurman#import "EXEmbeddedHomeLoader.h" 18*19a0af8dSWill Schurman#import "EXBuildConstants.h" 197f6367d0SBen Roth 20a859e3aeSWojciech Dróżdż@import ExpoScreenOrientation; 21a859e3aeSWojciech Dróżdż 2266169681SBen RothNSString * const kEXHomeDisableNuxDefaultsKey = @"EXKernelDisableNuxDefaultsKey"; 2366169681SBen RothNSString * const kEXHomeIsNuxFinishedDefaultsKey = @"EXHomeIsNuxFinishedDefaultsKey"; 2466169681SBen Roth 257f6367d0SBen RothNS_ASSUME_NONNULL_BEGIN 267f6367d0SBen Roth 27b987b50fSBen Roth@interface EXRootViewController () <EXAppBrowserController> 287f6367d0SBen Roth 29edc8619eSBen Roth@property (nonatomic, assign) BOOL isAnimatingAppTransition; 30a859e3aeSWojciech Dróżdż@property (nonatomic, weak) UIViewController *transitioningToViewController; 317f6367d0SBen Roth 327f6367d0SBen Roth@end 337f6367d0SBen Roth 347f6367d0SBen Roth@implementation EXRootViewController 357f6367d0SBen Roth 367f6367d0SBen Roth- (instancetype)init 377f6367d0SBen Roth{ 387f6367d0SBen Roth if (self = [super init]) { 397f6367d0SBen Roth [EXKernel sharedInstance].browserController = self; 4089fd6b24SBen Roth [self _maybeResetNuxState]; 417f6367d0SBen Roth } 427f6367d0SBen Roth return self; 437f6367d0SBen Roth} 447f6367d0SBen Roth 4575d03742SBartłomiej Bukowski#pragma mark - Screen Orientation 4675d03742SBartłomiej Bukowski 4775d03742SBartłomiej Bukowski- (BOOL)shouldAutorotate 4875d03742SBartłomiej Bukowski{ 4975d03742SBartłomiej Bukowski return YES; 5075d03742SBartłomiej Bukowski} 5175d03742SBartłomiej Bukowski 5275d03742SBartłomiej Bukowski/** 5375d03742SBartłomiej Bukowski * supportedInterfaceOrienation has to defined by the currently visible app (to support multiple apps with different settings), 5475d03742SBartłomiej Bukowski * but according to the iOS docs 'Typically, the system calls this method only on the root view controller of the window', 5575d03742SBartłomiej Bukowski * so we need to query the kernel about currently visible app and it's view controller settings 5675d03742SBartłomiej Bukowski */ 5775d03742SBartłomiej Bukowski- (UIInterfaceOrientationMask)supportedInterfaceOrientations 5875d03742SBartłomiej Bukowski{ 59a859e3aeSWojciech Dróżdż // During app transition we want to return the orientation of the screen that will be shown. This makes sure 60a859e3aeSWojciech Dróżdż // that the rotation animation starts as the new view controller is being shown. 61a859e3aeSWojciech Dróżdż if (_isAnimatingAppTransition && _transitioningToViewController != nil) { 62a859e3aeSWojciech Dróżdż return [_transitioningToViewController supportedInterfaceOrientations]; 63a859e3aeSWojciech Dróżdż } 64a859e3aeSWojciech Dróżdż 6575d03742SBartłomiej Bukowski const UIInterfaceOrientationMask visibleAppSupportedInterfaceOrientations = 6675d03742SBartłomiej Bukowski [EXKernel sharedInstance] 6775d03742SBartłomiej Bukowski .visibleApp 6875d03742SBartłomiej Bukowski .viewController 6975d03742SBartłomiej Bukowski .supportedInterfaceOrientations; 70a859e3aeSWojciech Dróżdż 7175d03742SBartłomiej Bukowski return visibleAppSupportedInterfaceOrientations; 7275d03742SBartłomiej Bukowski} 7375d03742SBartłomiej Bukowski 7489fd6b24SBen Roth#pragma mark - EXViewController 757f6367d0SBen Roth 7689fd6b24SBen Roth- (void)createRootAppAndMakeVisible 7789fd6b24SBen Roth{ 787f6367d0SBen Roth EXHomeAppManager *homeAppManager = [[EXHomeAppManager alloc] init]; 79*19a0af8dSWill Schurman 80*19a0af8dSWill Schurman // if developing, use development manifest from EXBuildConstants 81*19a0af8dSWill Schurman EXAbstractLoader *homeAppLoader; 82*19a0af8dSWill Schurman if ([EXBuildConstants sharedInstance].isDevKernel) { 83*19a0af8dSWill Schurman homeAppLoader = [[EXDevelopmentHomeLoader alloc] init]; 84*19a0af8dSWill Schurman } else { 85*19a0af8dSWill Schurman homeAppLoader = [[EXEmbeddedHomeLoader alloc] init]; 86*19a0af8dSWill Schurman } 87*19a0af8dSWill Schurman 887f6367d0SBen Roth EXKernelAppRecord *homeAppRecord = [[EXKernelAppRecord alloc] initWithAppLoader:homeAppLoader appManager:homeAppManager]; 897f6367d0SBen Roth [[EXKernel sharedInstance].appRegistry registerHomeAppRecord:homeAppRecord]; 907f6367d0SBen Roth [self moveAppToVisible:homeAppRecord]; 917f6367d0SBen Roth} 927f6367d0SBen Roth 937f6367d0SBen Roth#pragma mark - EXAppBrowserController 947f6367d0SBen Roth 957f6367d0SBen Roth- (void)moveAppToVisible:(EXKernelAppRecord *)appRecord 967f6367d0SBen Roth{ 977f6367d0SBen Roth [self _foregroundAppRecord:appRecord]; 98bbef49b2SBrent Vatne 99bbef49b2SBrent Vatne // When foregrounding the app record we want to add it to the history to handle the edge case 100bbef49b2SBrent Vatne // where a user opened a project, then went to home and cleared history, then went back to a 101bbef49b2SBrent Vatne // the already open project. 102bbef49b2SBrent Vatne [self addHistoryItemWithUrl:appRecord.appLoader.manifestUrl manifest:appRecord.appLoader.manifest]; 103373d5b21SBrent Vatne 1047f6367d0SBen Roth} 1057f6367d0SBen Roth 1062fcba380SBen Roth- (void)showQRReader 1072fcba380SBen Roth{ 1082fcba380SBen Roth [self moveHomeToVisible]; 109f44e163eSBen Roth [[self _getHomeAppManager] showQRReader]; 110b987b50fSBen Roth} 111b987b50fSBen Roth 112b987b50fSBen Roth- (void)moveHomeToVisible 113b987b50fSBen Roth{ 114f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] close]; 115f67462bcSTomasz Sapeta [self moveAppToVisible:[EXKernel sharedInstance].appRegistry.homeAppRecord]; 116b987b50fSBen Roth} 117b987b50fSBen Roth 118937e5c73SBrent Vatne- (BOOL)_isHomeVisible { 119937e5c73SBrent Vatne return [EXKernel sharedInstance].appRegistry.homeAppRecord == [EXKernel sharedInstance].visibleApp; 120937e5c73SBrent Vatne} 121937e5c73SBrent Vatne 122928bc3baSBen Roth// this is different from Util.reload() 123928bc3baSBen Roth// because it can work even on an errored app record (e.g. with no manifest, or with no running bridge). 124b333b418SBrent Vatne- (void)reloadVisibleApp 125b333b418SBrent Vatne{ 126937e5c73SBrent Vatne if ([self _isHomeVisible]) { 127937e5c73SBrent Vatne EXReactAppManager *homeAppManager = [EXKernel sharedInstance].appRegistry.homeAppRecord.appManager; 128937e5c73SBrent Vatne // reloadBridge will only reload the app if developer tools are enabled for the app 129937e5c73SBrent Vatne [homeAppManager reloadBridge]; 130937e5c73SBrent Vatne return; 131937e5c73SBrent Vatne } 132937e5c73SBrent Vatne 133f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] close]; 134b333b418SBrent Vatne 13585f025b0SBen Roth EXKernelAppRecord *visibleApp = [EXKernel sharedInstance].visibleApp; 13685f025b0SBen Roth NSURL *urlToRefresh = visibleApp.appLoader.manifestUrl; 1373b6a24fbSTomasz Sapeta 1383b6a24fbSTomasz Sapeta // Unregister visible app record so all modules get destroyed. 1393b6a24fbSTomasz Sapeta [[[EXKernel sharedInstance] appRegistry] unregisterAppWithRecord:visibleApp]; 1403b6a24fbSTomasz Sapeta 1413b6a24fbSTomasz Sapeta // Create new app record. 142b987b50fSBen Roth [[EXKernel sharedInstance] createNewAppWithUrl:urlToRefresh initialProps:nil]; 143b987b50fSBen Roth} 144b987b50fSBen Roth 14550661f5cSWill Schurman- (void)addHistoryItemWithUrl:(NSURL *)manifestUrl manifest:(EXManifestsManifest *)manifest 146f44e163eSBen Roth{ 147f44e163eSBen Roth [[self _getHomeAppManager] addHistoryItemWithUrl:manifestUrl manifest:manifest]; 148f44e163eSBen Roth} 149f44e163eSBen Roth 150167fd314SWill Schurman- (void)getHistoryUrlForScopeKey:(NSString *)scopeKey completion:(void (^)(NSString *))completion 151f44e163eSBen Roth{ 152167fd314SWill Schurman return [[self _getHomeAppManager] getHistoryUrlForScopeKey:scopeKey completion:completion]; 153f44e163eSBen Roth} 154f44e163eSBen Roth 15566169681SBen Roth- (void)setIsNuxFinished:(BOOL)isFinished 15666169681SBen Roth{ 15766169681SBen Roth [[NSUserDefaults standardUserDefaults] setBool:isFinished forKey:kEXHomeIsNuxFinishedDefaultsKey]; 15866169681SBen Roth [[NSUserDefaults standardUserDefaults] synchronize]; 15966169681SBen Roth} 16066169681SBen Roth 16166169681SBen Roth- (BOOL)isNuxFinished 16266169681SBen Roth{ 16366169681SBen Roth return [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeIsNuxFinishedDefaultsKey]; 16466169681SBen Roth} 16566169681SBen Roth 16666169681SBen Roth- (void)appDidFinishLoadingSuccessfully:(EXKernelAppRecord *)appRecord 16766169681SBen Roth{ 16866169681SBen Roth // show nux if needed 16966169681SBen Roth if (!self.isNuxFinished 17066169681SBen Roth && appRecord == [EXKernel sharedInstance].visibleApp 171f67462bcSTomasz Sapeta && appRecord != [EXKernel sharedInstance].appRegistry.homeAppRecord) { 172f67462bcSTomasz Sapeta [[EXDevMenuManager sharedInstance] open]; 17366169681SBen Roth } 174a859e3aeSWojciech Dróżdż 175a859e3aeSWojciech Dróżdż // Re-apply the default orientation after the app has been loaded (eq. after a reload) 176a859e3aeSWojciech Dróżdż [self _applySupportedInterfaceOrientations]; 17766169681SBen Roth} 17866169681SBen Roth 179b987b50fSBen Roth#pragma mark - internal 180b987b50fSBen Roth 1817f6367d0SBen Roth- (void)_foregroundAppRecord:(EXKernelAppRecord *)appRecord 1827f6367d0SBen Roth{ 183c8452ff7SBartłomiej Bukowski // Some transition is in progress 184edc8619eSBen Roth if (_isAnimatingAppTransition) { 185edc8619eSBen Roth return; 1867f6367d0SBen Roth } 187c8452ff7SBartłomiej Bukowski 188edc8619eSBen Roth EXAppViewController *viewControllerToShow = appRecord.viewController; 189a859e3aeSWojciech Dróżdż _transitioningToViewController = viewControllerToShow; 190c8452ff7SBartłomiej Bukowski 191a859e3aeSWojciech Dróżdż // Tried to foreground the very same view controller 192c8452ff7SBartłomiej Bukowski if (viewControllerToShow == self.contentViewController) { 193c8452ff7SBartłomiej Bukowski return; 1947f6367d0SBen Roth } 1957f6367d0SBen Roth 196c8452ff7SBartłomiej Bukowski _isAnimatingAppTransition = YES; 197c8452ff7SBartłomiej Bukowski 198c8452ff7SBartłomiej Bukowski EXAppViewController *viewControllerToHide = (EXAppViewController *)self.contentViewController; 199c8452ff7SBartłomiej Bukowski 200c8452ff7SBartłomiej Bukowski if (viewControllerToShow) { 201c8452ff7SBartłomiej Bukowski [self.view addSubview:viewControllerToShow.view]; 202c8452ff7SBartłomiej Bukowski [self addChildViewController:viewControllerToShow]; 203c8452ff7SBartłomiej Bukowski } 204c8452ff7SBartłomiej Bukowski 205a859e3aeSWojciech Dróżdż // Try transitioning to the interface orientation of the app before it is shown for smoother transitions 206a859e3aeSWojciech Dróżdż [self _applySupportedInterfaceOrientations]; 207a859e3aeSWojciech Dróżdż 208c8452ff7SBartłomiej Bukowski EX_WEAKIFY(self) 209c8452ff7SBartłomiej Bukowski void (^finalizeTransition)(void) = ^{ 210c8452ff7SBartłomiej Bukowski EX_ENSURE_STRONGIFY(self) 211edc8619eSBen Roth if (viewControllerToHide) { 212ce63205bSTomasz Sapeta // backgrounds and then dismisses all modals that are presented by the app 213ce63205bSTomasz Sapeta [viewControllerToHide backgroundControllers]; 214d57b5fd1SBrent Vatne [viewControllerToHide dismissViewControllerAnimated:NO completion:nil]; 215edc8619eSBen Roth [viewControllerToHide willMoveToParentViewController:nil]; 216c8452ff7SBartłomiej Bukowski [viewControllerToHide removeFromParentViewController]; 217edc8619eSBen Roth [viewControllerToHide.view removeFromSuperview]; 218e7ac6e4fSBen Roth } 219c8452ff7SBartłomiej Bukowski 220edc8619eSBen Roth if (viewControllerToShow) { 221c8452ff7SBartłomiej Bukowski [viewControllerToShow didMoveToParentViewController:self]; 222c8452ff7SBartłomiej Bukowski self.contentViewController = viewControllerToShow; 223edc8619eSBen Roth } 224c8452ff7SBartłomiej Bukowski 225c8452ff7SBartłomiej Bukowski [self.view setNeedsLayout]; 226c8452ff7SBartłomiej Bukowski self.isAnimatingAppTransition = NO; 227a859e3aeSWojciech Dróżdż self.transitioningToViewController = nil; 228c8452ff7SBartłomiej Bukowski if (self.delegate) { 229c8452ff7SBartłomiej Bukowski [self.delegate viewController:self didNavigateAppToVisible:appRecord]; 230edc8619eSBen Roth } 231a859e3aeSWojciech Dróżdż [self _applySupportedInterfaceOrientations]; 232edc8619eSBen Roth }; 233edc8619eSBen Roth 234edc8619eSBen Roth BOOL animated = (viewControllerToHide && viewControllerToShow); 235c8452ff7SBartłomiej Bukowski if (!animated) { 236c8452ff7SBartłomiej Bukowski return finalizeTransition(); 237c8452ff7SBartłomiej Bukowski } 238c8452ff7SBartłomiej Bukowski 239edc8619eSBen Roth if (viewControllerToHide.contentView) { 240edc8619eSBen Roth viewControllerToHide.contentView.transform = CGAffineTransformIdentity; 241edc8619eSBen Roth viewControllerToHide.contentView.alpha = 1.0f; 242edc8619eSBen Roth } 243edc8619eSBen Roth if (viewControllerToShow.contentView) { 244edc8619eSBen Roth viewControllerToShow.contentView.transform = CGAffineTransformMakeScale(1.1f, 1.1f); 245edc8619eSBen Roth viewControllerToShow.contentView.alpha = 0; 246edc8619eSBen Roth } 247c8452ff7SBartłomiej Bukowski 248edc8619eSBen Roth [UIView animateWithDuration:0.3f animations:^{ 249edc8619eSBen Roth if (viewControllerToHide.contentView) { 250edc8619eSBen Roth viewControllerToHide.contentView.transform = CGAffineTransformMakeScale(0.95f, 0.95f); 251edc8619eSBen Roth viewControllerToHide.contentView.alpha = 0.5f; 252edc8619eSBen Roth } 253edc8619eSBen Roth if (viewControllerToShow.contentView) { 254edc8619eSBen Roth viewControllerToShow.contentView.transform = CGAffineTransformIdentity; 255edc8619eSBen Roth viewControllerToShow.contentView.alpha = 1.0f; 256edc8619eSBen Roth } 257edc8619eSBen Roth } completion:^(BOOL finished) { 258c8452ff7SBartłomiej Bukowski finalizeTransition(); 259edc8619eSBen Roth }]; 260edc8619eSBen Roth} 261edc8619eSBen Roth 262f44e163eSBen Roth- (EXHomeAppManager *)_getHomeAppManager 263f44e163eSBen Roth{ 264f44e163eSBen Roth return (EXHomeAppManager *)[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager; 265f44e163eSBen Roth} 266f44e163eSBen Roth 26766169681SBen Roth- (void)_maybeResetNuxState 26866169681SBen Roth{ 26966169681SBen Roth // used by appetize: optionally disable nux 27066169681SBen Roth BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeDisableNuxDefaultsKey]; 27166169681SBen Roth if (disableNuxDefaultsValue) { 27266169681SBen Roth [self setIsNuxFinished:YES]; 27366169681SBen Roth [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXHomeDisableNuxDefaultsKey]; 27466169681SBen Roth } 27566169681SBen Roth} 27666169681SBen Roth 277a859e3aeSWojciech Dróżdż- (void)_applySupportedInterfaceOrientations 278a859e3aeSWojciech Dróżdż{ 279a859e3aeSWojciech Dróżdż if (@available(iOS 16, *)) { 280a859e3aeSWojciech Dróżdż [self setNeedsUpdateOfSupportedInterfaceOrientations]; 281a859e3aeSWojciech Dróżdż } else { 282a859e3aeSWojciech Dróżdż // On iOS < 16 we need to try to rotate to the desired orientation, which also 283a859e3aeSWojciech Dróżdż // makes the view controller to update the supported orientations 284a859e3aeSWojciech Dróżdż UIInterfaceOrientationMask orientationMask = [self supportedInterfaceOrientations]; 285a859e3aeSWojciech Dróżdż [ScreenOrientationRegistry.shared enforceDesiredDeviceOrientationWithOrientationMask:orientationMask]; 286a859e3aeSWojciech Dróżdż } 287a859e3aeSWojciech Dróżdż} 288a859e3aeSWojciech Dróżdż 2897f6367d0SBen Roth@end 2907f6367d0SBen Roth 2917f6367d0SBen RothNS_ASSUME_NONNULL_END 292