xref: /expo/ios/Client/EXRootViewController.m (revision b4759e5f)
1// Copyright 2015-present 650 Industries. All rights reserved.
2
3@import UIKit;
4
5#import "EXAppViewController.h"
6#import "EXHomeAppManager.h"
7#import "EXKernel.h"
8#import "EXAppLoader.h"
9#import "EXKernelAppRecord.h"
10#import "EXKernelAppRegistry.h"
11#import "EXKernelLinkingManager.h"
12#import "EXKernelServiceRegistry.h"
13#import "EXRootViewController.h"
14#import "EXDevMenuManager.h"
15
16NSString * const kEXHomeDisableNuxDefaultsKey = @"EXKernelDisableNuxDefaultsKey";
17NSString * const kEXHomeIsNuxFinishedDefaultsKey = @"EXHomeIsNuxFinishedDefaultsKey";
18
19NS_ASSUME_NONNULL_BEGIN
20
21@interface EXRootViewController () <EXAppBrowserController>
22
23@property (nonatomic, assign) BOOL isAnimatingAppTransition;
24
25@end
26
27@implementation EXRootViewController
28
29- (instancetype)init
30{
31  if (self = [super init]) {
32    [EXKernel sharedInstance].browserController = self;
33    [self _maybeResetNuxState];
34  }
35  return self;
36}
37
38#pragma mark - EXViewController
39
40- (void)createRootAppAndMakeVisible
41{
42  EXHomeAppManager *homeAppManager = [[EXHomeAppManager alloc] init];
43  EXAppLoader *homeAppLoader = [[EXAppLoader alloc] initWithLocalManifest:[EXHomeAppManager bundledHomeManifest]];
44  EXKernelAppRecord *homeAppRecord = [[EXKernelAppRecord alloc] initWithAppLoader:homeAppLoader appManager:homeAppManager];
45  [[EXKernel sharedInstance].appRegistry registerHomeAppRecord:homeAppRecord];
46  [self moveAppToVisible:homeAppRecord];
47}
48
49#pragma mark - EXAppBrowserController
50
51- (void)moveAppToVisible:(EXKernelAppRecord *)appRecord
52{
53  [self _foregroundAppRecord:appRecord];
54
55  // When foregrounding the app record we want to add it to the history to handle the edge case
56  // where a user opened a project, then went to home and cleared history, then went back to a
57  // the already open project.
58  [self addHistoryItemWithUrl:appRecord.appLoader.manifestUrl manifest:appRecord.appLoader.manifest];
59
60}
61
62- (void)showQRReader
63{
64  [self moveHomeToVisible];
65  [[self _getHomeAppManager] showQRReader];
66}
67
68- (void)moveHomeToVisible
69{
70  [[EXDevMenuManager sharedInstance] close];
71  [self moveAppToVisible:[EXKernel sharedInstance].appRegistry.homeAppRecord];
72}
73
74- (BOOL)_isHomeVisible {
75  return [EXKernel sharedInstance].appRegistry.homeAppRecord == [EXKernel sharedInstance].visibleApp;
76}
77
78// this is different from Util.reload()
79// because it can work even on an errored app record (e.g. with no manifest, or with no running bridge).
80- (void)reloadVisibleApp
81{
82  if ([self _isHomeVisible]) {
83    EXReactAppManager *homeAppManager = [EXKernel sharedInstance].appRegistry.homeAppRecord.appManager;
84    // reloadBridge will only reload the app if developer tools are enabled for the app
85    [homeAppManager reloadBridge];
86    return;
87  }
88
89  [[EXDevMenuManager sharedInstance] close];
90
91  EXKernelAppRecord *visibleApp = [EXKernel sharedInstance].visibleApp;
92  [[EXKernel sharedInstance] logAnalyticsEvent:@"RELOAD_EXPERIENCE" forAppRecord:visibleApp];
93  NSURL *urlToRefresh = visibleApp.appLoader.manifestUrl;
94
95  // Unregister visible app record so all modules get destroyed.
96  [[[EXKernel sharedInstance] appRegistry] unregisterAppWithRecord:visibleApp];
97
98  // Create new app record.
99  [[EXKernel sharedInstance] createNewAppWithUrl:urlToRefresh initialProps:nil];
100}
101
102- (void)addHistoryItemWithUrl:(NSURL *)manifestUrl manifest:(EXUpdatesRawManifest *)manifest
103{
104  [[self _getHomeAppManager] addHistoryItemWithUrl:manifestUrl manifest:manifest];
105}
106
107- (void)getHistoryUrlForExperienceId:(NSString *)experienceId completion:(void (^)(NSString *))completion
108{
109  return [[self _getHomeAppManager] getHistoryUrlForExperienceId:experienceId completion:completion];
110}
111
112- (void)setIsNuxFinished:(BOOL)isFinished
113{
114  [[NSUserDefaults standardUserDefaults] setBool:isFinished forKey:kEXHomeIsNuxFinishedDefaultsKey];
115  [[NSUserDefaults standardUserDefaults] synchronize];
116}
117
118- (BOOL)isNuxFinished
119{
120  return [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeIsNuxFinishedDefaultsKey];
121}
122
123- (void)appDidFinishLoadingSuccessfully:(EXKernelAppRecord *)appRecord
124{
125  // show nux if needed
126  if (!self.isNuxFinished
127      && appRecord == [EXKernel sharedInstance].visibleApp
128      && appRecord != [EXKernel sharedInstance].appRegistry.homeAppRecord) {
129    [[EXDevMenuManager sharedInstance] open];
130  }
131}
132
133#pragma mark - internal
134
135- (void)_foregroundAppRecord:(EXKernelAppRecord *)appRecord
136{
137  if (_isAnimatingAppTransition) {
138    return;
139  }
140  EXAppViewController *viewControllerToShow = appRecord.viewController;
141  EXAppViewController *viewControllerToHide;
142  if (viewControllerToShow != self.contentViewController) {
143    _isAnimatingAppTransition = YES;
144    if (self.contentViewController) {
145      viewControllerToHide = (EXAppViewController *)self.contentViewController;
146    }
147    if (viewControllerToShow) {
148      [viewControllerToShow willMoveToParentViewController:self];
149      [self.view addSubview:viewControllerToShow.view];
150      [viewControllerToShow foregroundControllers];
151    }
152
153    __weak typeof(self) weakSelf = self;
154    void (^transitionFinished)(void) = ^{
155      __strong typeof(weakSelf) strongSelf = weakSelf;
156      if (strongSelf) {
157        if (viewControllerToHide) {
158          // backgrounds and then dismisses all modals that are presented by the app
159          [viewControllerToHide backgroundControllers];
160          [viewControllerToHide dismissViewControllerAnimated:NO completion:nil];
161          [viewControllerToHide willMoveToParentViewController:nil];
162          [viewControllerToHide.view removeFromSuperview];
163          [viewControllerToHide didMoveToParentViewController:nil];
164        }
165        if (viewControllerToShow) {
166          [viewControllerToShow didMoveToParentViewController:strongSelf];
167          strongSelf.contentViewController = viewControllerToShow;
168        }
169        [strongSelf.view setNeedsLayout];
170        strongSelf.isAnimatingAppTransition = NO;
171        if (strongSelf.delegate) {
172          [strongSelf.delegate viewController:strongSelf didNavigateAppToVisible:appRecord];
173        }
174      }
175    };
176
177    BOOL animated = (viewControllerToHide && viewControllerToShow);
178    if (animated) {
179      if (viewControllerToHide.contentView) {
180        viewControllerToHide.contentView.transform = CGAffineTransformIdentity;
181        viewControllerToHide.contentView.alpha = 1.0f;
182      }
183      if (viewControllerToShow.contentView) {
184        viewControllerToShow.contentView.transform = CGAffineTransformMakeScale(1.1f, 1.1f);
185        viewControllerToShow.contentView.alpha = 0;
186      }
187      [UIView animateWithDuration:0.3f animations:^{
188        if (viewControllerToHide.contentView) {
189          viewControllerToHide.contentView.transform = CGAffineTransformMakeScale(0.95f, 0.95f);
190          viewControllerToHide.contentView.alpha = 0.5f;
191        }
192        if (viewControllerToShow.contentView) {
193          viewControllerToShow.contentView.transform = CGAffineTransformIdentity;
194          viewControllerToShow.contentView.alpha = 1.0f;
195        }
196      } completion:^(BOOL finished) {
197        transitionFinished();
198      }];
199    } else {
200      transitionFinished();
201    }
202  }
203}
204
205- (EXHomeAppManager *)_getHomeAppManager
206{
207  return (EXHomeAppManager *)[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager;
208}
209
210- (void)_maybeResetNuxState
211{
212  // used by appetize: optionally disable nux
213  BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeDisableNuxDefaultsKey];
214  if (disableNuxDefaultsValue) {
215    [self setIsNuxFinished:YES];
216    [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXHomeDisableNuxDefaultsKey];
217  }
218}
219
220@end
221
222NS_ASSUME_NONNULL_END
223