17f6367d0SBen Roth// Copyright 2015-present 650 Industries. All rights reserved.
27f6367d0SBen Roth
37f6367d0SBen Roth@import UIKit;
47f6367d0SBen Roth
57f6367d0SBen Roth#import "EXAppDelegate.h"
67f6367d0SBen Roth#import "EXAppViewController.h"
7b0932293SBen Roth#import "EXButtonView.h"
87f6367d0SBen Roth#import "EXHomeAppManager.h"
97f6367d0SBen Roth#import "EXHomeDiagnosticsViewController.h"
107f6367d0SBen Roth#import "EXKernel.h"
119088dd0eSBen Roth#import "EXAppLoader.h"
127f6367d0SBen Roth#import "EXKernelAppRecord.h"
137f6367d0SBen Roth#import "EXKernelAppRegistry.h"
14b0932293SBen Roth#import "EXKernelDevKeyCommands.h"
15f322f621SBen Roth#import "EXKernelLinkingManager.h"
167f6367d0SBen Roth#import "EXKernelServiceRegistry.h"
174e9980b6SBen Roth#import "EXMenuGestureRecognizer.h"
187f6367d0SBen Roth#import "EXMenuViewController.h"
197f6367d0SBen Roth#import "EXRootViewController.h"
203980c593STomasz Sapeta#import "EXMenuWindow.h"
217f6367d0SBen Roth
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
297f6367d0SBen Roth@property (nonatomic, strong) EXMenuViewController *menuViewController;
307f6367d0SBen Roth@property (nonatomic, assign) BOOL isMenuVisible;
31edc8619eSBen Roth@property (nonatomic, assign) BOOL isAnimatingMenu;
32edc8619eSBen Roth@property (nonatomic, assign) BOOL isAnimatingAppTransition;
33b0932293SBen Roth@property (nonatomic, strong) EXButtonView *btnMenu;
34d718b4b8SBen Roth@property (nonatomic, strong, nullable) EXMenuWindow *menuWindow;
357f6367d0SBen Roth
367f6367d0SBen Roth@end
377f6367d0SBen Roth
387f6367d0SBen Roth@implementation EXRootViewController
397f6367d0SBen Roth
407f6367d0SBen Roth- (instancetype)init
417f6367d0SBen Roth{
427f6367d0SBen Roth  if (self = [super init]) {
437f6367d0SBen Roth    [EXKernel sharedInstance].browserController = self;
44b0932293SBen Roth    [[NSNotificationCenter defaultCenter] addObserver:self
454e9980b6SBen Roth                                             selector:@selector(_updateMenuButtonBehavior)
46b0932293SBen Roth                                                 name:kEXKernelDidChangeMenuBehaviorNotification
47b0932293SBen Roth                                               object:nil];
4889fd6b24SBen Roth    [self _maybeResetNuxState];
497f6367d0SBen Roth  }
507f6367d0SBen Roth  return self;
517f6367d0SBen Roth}
527f6367d0SBen Roth
53b0932293SBen Roth- (void)viewDidLoad
54b0932293SBen Roth{
55b0932293SBen Roth  [super viewDidLoad];
56b0932293SBen Roth  _btnMenu = [[EXButtonView alloc] init];
57b0932293SBen Roth  _btnMenu.hidden = YES;
58b0932293SBen Roth  [self.view addSubview:_btnMenu];
594e9980b6SBen Roth  EXMenuGestureRecognizer *menuGestureRecognizer = [[EXMenuGestureRecognizer alloc] initWithTarget:self action:@selector(_onMenuGestureRecognized:)];
604e9980b6SBen Roth  [((EXAppDelegate *)[UIApplication sharedApplication].delegate).window addGestureRecognizer:menuGestureRecognizer];
61b0932293SBen Roth}
62b0932293SBen Roth
63b0932293SBen Roth- (void)viewWillLayoutSubviews
64b0932293SBen Roth{
65b0932293SBen Roth  [super viewWillLayoutSubviews];
66b0932293SBen Roth  _btnMenu.frame = CGRectMake(0, 0, 48.0f, 48.0f);
67b0932293SBen Roth  _btnMenu.center = CGPointMake(self.view.frame.size.width - 36.0f, self.view.frame.size.height - 72.0f);
68b0932293SBen Roth  [self.view bringSubviewToFront:_btnMenu];
69b0932293SBen Roth}
70b0932293SBen Roth
7189fd6b24SBen Roth#pragma mark - EXViewController
727f6367d0SBen Roth
7389fd6b24SBen Roth- (void)createRootAppAndMakeVisible
7489fd6b24SBen Roth{
757f6367d0SBen Roth  EXHomeAppManager *homeAppManager = [[EXHomeAppManager alloc] init];
769088dd0eSBen Roth  EXAppLoader *homeAppLoader = [[EXAppLoader alloc] initWithLocalManifest:[EXHomeAppManager bundledHomeManifest]];
777f6367d0SBen Roth  EXKernelAppRecord *homeAppRecord = [[EXKernelAppRecord alloc] initWithAppLoader:homeAppLoader appManager:homeAppManager];
787f6367d0SBen Roth  [[EXKernel sharedInstance].appRegistry registerHomeAppRecord:homeAppRecord];
797f6367d0SBen Roth  [self moveAppToVisible:homeAppRecord];
807f6367d0SBen Roth}
817f6367d0SBen Roth
827f6367d0SBen Roth#pragma mark - EXAppBrowserController
837f6367d0SBen Roth
847f6367d0SBen Roth- (void)moveAppToVisible:(EXKernelAppRecord *)appRecord
857f6367d0SBen Roth{
867f6367d0SBen Roth  [self _foregroundAppRecord:appRecord];
87*bbef49b2SBrent Vatne
88*bbef49b2SBrent Vatne  // When foregrounding the app record we want to add it to the history to handle the edge case
89*bbef49b2SBrent Vatne  // where a user opened a project, then went to home and cleared history, then went back to a
90*bbef49b2SBrent Vatne  // the already open project.
91*bbef49b2SBrent Vatne  [self addHistoryItemWithUrl:appRecord.appLoader.manifestUrl manifest:appRecord.appLoader.manifest];
927f6367d0SBen Roth}
937f6367d0SBen Roth
94edc8619eSBen Roth- (void)toggleMenuWithCompletion:(void (^ _Nullable)(void))completion
957f6367d0SBen Roth{
96edc8619eSBen Roth  [self setIsMenuVisible:!_isMenuVisible completion:completion];
977f6367d0SBen Roth}
987f6367d0SBen Roth
99edc8619eSBen Roth- (void)setIsMenuVisible:(BOOL)isMenuVisible completion:(void (^ _Nullable)(void))completion
1007f6367d0SBen Roth{
1017f6367d0SBen Roth  if (!_menuViewController) {
1027f6367d0SBen Roth    _menuViewController = [[EXMenuViewController alloc] init];
1037f6367d0SBen Roth  }
104bddeb74fSBen Roth  if (isMenuVisible != _isMenuVisible) {
105bddeb74fSBen Roth    if (!_isAnimatingMenu) {
1067f6367d0SBen Roth      _isMenuVisible = isMenuVisible;
107edc8619eSBen Roth      [self _animateMenuToVisible:_isMenuVisible completion:completion];
1087f6367d0SBen Roth    }
109bddeb74fSBen Roth  } else {
110bddeb74fSBen Roth    completion();
111bddeb74fSBen Roth  }
1127f6367d0SBen Roth}
1137f6367d0SBen Roth
114b987b50fSBen Roth- (void)showDiagnostics
115b987b50fSBen Roth{
116edc8619eSBen Roth  __weak typeof(self) weakSelf = self;
117edc8619eSBen Roth  [self setIsMenuVisible:NO completion:^{
118edc8619eSBen Roth    __strong typeof(weakSelf) strongSelf = weakSelf;
119edc8619eSBen Roth    if (strongSelf) {
120b987b50fSBen Roth      EXHomeDiagnosticsViewController *vcDiagnostics = [[EXHomeDiagnosticsViewController alloc] init];
121edc8619eSBen Roth      [strongSelf presentViewController:vcDiagnostics animated:NO completion:nil];
122edc8619eSBen Roth    }
123edc8619eSBen Roth  }];
124b987b50fSBen Roth}
125b987b50fSBen Roth
1262fcba380SBen Roth- (void)showQRReader
1272fcba380SBen Roth{
1282fcba380SBen Roth  [self moveHomeToVisible];
129f44e163eSBen Roth  [[self _getHomeAppManager] showQRReader];
130b987b50fSBen Roth}
131b987b50fSBen Roth
132b987b50fSBen Roth- (void)moveHomeToVisible
133b987b50fSBen Roth{
134edc8619eSBen Roth  __weak typeof(self) weakSelf = self;
135edc8619eSBen Roth  [self setIsMenuVisible:NO completion:^{
136edc8619eSBen Roth    __strong typeof(weakSelf) strongSelf = weakSelf;
137edc8619eSBen Roth    if (strongSelf) {
138edc8619eSBen Roth      [strongSelf moveAppToVisible:[EXKernel sharedInstance].appRegistry.homeAppRecord];
1393980c593STomasz Sapeta
1403980c593STomasz Sapeta      if (strongSelf.isMenuVisible) {
1413980c593STomasz Sapeta        [strongSelf setIsMenuVisible:NO completion:nil];
1423980c593STomasz Sapeta      }
143edc8619eSBen Roth    }
144edc8619eSBen Roth  }];
145b987b50fSBen Roth}
146b987b50fSBen Roth
147b987b50fSBen Roth- (void)refreshVisibleApp
148b987b50fSBen Roth{
149928bc3baSBen Roth  // this is different from Util.reload()
150928bc3baSBen Roth  // because it can work even on an errored app record (e.g. with no manifest, or with no running bridge).
151edc8619eSBen Roth  [self setIsMenuVisible:NO completion:nil];
15285f025b0SBen Roth  EXKernelAppRecord *visibleApp = [EXKernel sharedInstance].visibleApp;
15385f025b0SBen Roth  [[EXKernel sharedInstance] logAnalyticsEvent:@"RELOAD_EXPERIENCE" forAppRecord:visibleApp];
15485f025b0SBen Roth  NSURL *urlToRefresh = visibleApp.appLoader.manifestUrl;
1553b6a24fbSTomasz Sapeta
1563b6a24fbSTomasz Sapeta  // Unregister visible app record so all modules get destroyed.
1573b6a24fbSTomasz Sapeta  [[[EXKernel sharedInstance] appRegistry] unregisterAppWithRecord:visibleApp];
1583b6a24fbSTomasz Sapeta
1593b6a24fbSTomasz Sapeta  // Create new app record.
160b987b50fSBen Roth  [[EXKernel sharedInstance] createNewAppWithUrl:urlToRefresh initialProps:nil];
161b987b50fSBen Roth}
162b987b50fSBen Roth
163f44e163eSBen Roth- (void)addHistoryItemWithUrl:(NSURL *)manifestUrl manifest:(NSDictionary *)manifest
164f44e163eSBen Roth{
165f44e163eSBen Roth  [[self _getHomeAppManager] addHistoryItemWithUrl:manifestUrl manifest:manifest];
166f44e163eSBen Roth}
167f44e163eSBen Roth
168f44e163eSBen Roth- (void)getHistoryUrlForExperienceId:(NSString *)experienceId completion:(void (^)(NSString *))completion
169f44e163eSBen Roth{
170f44e163eSBen Roth  return [[self _getHomeAppManager] getHistoryUrlForExperienceId:experienceId completion:completion];
171f44e163eSBen Roth}
172f44e163eSBen Roth
17366169681SBen Roth- (void)setIsNuxFinished:(BOOL)isFinished
17466169681SBen Roth{
17566169681SBen Roth  [[NSUserDefaults standardUserDefaults] setBool:isFinished forKey:kEXHomeIsNuxFinishedDefaultsKey];
17666169681SBen Roth  [[NSUserDefaults standardUserDefaults] synchronize];
17766169681SBen Roth}
17866169681SBen Roth
17966169681SBen Roth- (BOOL)isNuxFinished
18066169681SBen Roth{
18166169681SBen Roth  return [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeIsNuxFinishedDefaultsKey];
18266169681SBen Roth}
18366169681SBen Roth
18466169681SBen Roth- (void)appDidFinishLoadingSuccessfully:(EXKernelAppRecord *)appRecord
18566169681SBen Roth{
18666169681SBen Roth  // show nux if needed
18766169681SBen Roth  if (!self.isNuxFinished
18866169681SBen Roth      && appRecord == [EXKernel sharedInstance].visibleApp
18966169681SBen Roth      && appRecord != [EXKernel sharedInstance].appRegistry.homeAppRecord
19066169681SBen Roth      && !self.isMenuVisible) {
191edc8619eSBen Roth    [self setIsMenuVisible:YES completion:nil];
19266169681SBen Roth  }
193b0932293SBen Roth
1944e9980b6SBen Roth  // check button availability when any new app loads
1954e9980b6SBen Roth  [self _updateMenuButtonBehavior];
19666169681SBen Roth}
19766169681SBen Roth
198b987b50fSBen Roth#pragma mark - internal
199b987b50fSBen Roth
2007f6367d0SBen Roth- (void)_foregroundAppRecord:(EXKernelAppRecord *)appRecord
2017f6367d0SBen Roth{
202edc8619eSBen Roth  if (_isAnimatingAppTransition) {
203edc8619eSBen Roth    return;
2047f6367d0SBen Roth  }
205edc8619eSBen Roth  EXAppViewController *viewControllerToShow = appRecord.viewController;
206edc8619eSBen Roth  EXAppViewController *viewControllerToHide;
207edc8619eSBen Roth  if (viewControllerToShow != self.contentViewController) {
208edc8619eSBen Roth    _isAnimatingAppTransition = YES;
209edc8619eSBen Roth    if (self.contentViewController) {
210edc8619eSBen Roth      viewControllerToHide = (EXAppViewController *)self.contentViewController;
211edc8619eSBen Roth    }
2127f6367d0SBen Roth    if (viewControllerToShow) {
2137f6367d0SBen Roth      [viewControllerToShow willMoveToParentViewController:self];
2147f6367d0SBen Roth      [self.view addSubview:viewControllerToShow.view];
215ce63205bSTomasz Sapeta      [viewControllerToShow foregroundControllers];
2167f6367d0SBen Roth    }
2177f6367d0SBen Roth
218edc8619eSBen Roth    __weak typeof(self) weakSelf = self;
219edc8619eSBen Roth    void (^transitionFinished)(void) = ^{
220edc8619eSBen Roth      __strong typeof(weakSelf) strongSelf = weakSelf;
221edc8619eSBen Roth      if (strongSelf) {
222edc8619eSBen Roth        if (viewControllerToHide) {
223ce63205bSTomasz Sapeta          // backgrounds and then dismisses all modals that are presented by the app
224ce63205bSTomasz Sapeta          [viewControllerToHide backgroundControllers];
225d57b5fd1SBrent Vatne          [viewControllerToHide dismissViewControllerAnimated:NO completion:nil];
226edc8619eSBen Roth          [viewControllerToHide willMoveToParentViewController:nil];
227edc8619eSBen Roth          [viewControllerToHide.view removeFromSuperview];
228edc8619eSBen Roth          [viewControllerToHide didMoveToParentViewController:nil];
229e7ac6e4fSBen Roth        }
230edc8619eSBen Roth        if (viewControllerToShow) {
231edc8619eSBen Roth          [viewControllerToShow didMoveToParentViewController:strongSelf];
232edc8619eSBen Roth          strongSelf.contentViewController = viewControllerToShow;
233edc8619eSBen Roth        }
234edc8619eSBen Roth        [strongSelf.view setNeedsLayout];
235edc8619eSBen Roth        strongSelf.isAnimatingAppTransition = NO;
236edc8619eSBen Roth        if (strongSelf.delegate) {
237edc8619eSBen Roth          [strongSelf.delegate viewController:strongSelf didNavigateAppToVisible:appRecord];
238edc8619eSBen Roth        }
239edc8619eSBen Roth      }
240edc8619eSBen Roth    };
241edc8619eSBen Roth
242edc8619eSBen Roth    BOOL animated = (viewControllerToHide && viewControllerToShow);
243edc8619eSBen Roth    if (animated) {
244edc8619eSBen Roth      if (viewControllerToHide.contentView) {
245edc8619eSBen Roth        viewControllerToHide.contentView.transform = CGAffineTransformIdentity;
246edc8619eSBen Roth        viewControllerToHide.contentView.alpha = 1.0f;
247edc8619eSBen Roth      }
248edc8619eSBen Roth      if (viewControllerToShow.contentView) {
249edc8619eSBen Roth        viewControllerToShow.contentView.transform = CGAffineTransformMakeScale(1.1f, 1.1f);
250edc8619eSBen Roth        viewControllerToShow.contentView.alpha = 0;
251edc8619eSBen Roth      }
252edc8619eSBen Roth      [UIView animateWithDuration:0.3f animations:^{
253edc8619eSBen Roth        if (viewControllerToHide.contentView) {
254edc8619eSBen Roth          viewControllerToHide.contentView.transform = CGAffineTransformMakeScale(0.95f, 0.95f);
255edc8619eSBen Roth          viewControllerToHide.contentView.alpha = 0.5f;
256edc8619eSBen Roth        }
257edc8619eSBen Roth        if (viewControllerToShow.contentView) {
258edc8619eSBen Roth          viewControllerToShow.contentView.transform = CGAffineTransformIdentity;
259edc8619eSBen Roth          viewControllerToShow.contentView.alpha = 1.0f;
260edc8619eSBen Roth        }
261edc8619eSBen Roth      } completion:^(BOOL finished) {
262edc8619eSBen Roth        transitionFinished();
263edc8619eSBen Roth      }];
264edc8619eSBen Roth    } else {
265edc8619eSBen Roth      transitionFinished();
266edc8619eSBen Roth    }
267edc8619eSBen Roth  }
268edc8619eSBen Roth}
269edc8619eSBen Roth
270edc8619eSBen Roth- (void)_animateMenuToVisible:(BOOL)visible completion:(void (^ _Nullable)(void))completion
271edc8619eSBen Roth{
272edc8619eSBen Roth  _isAnimatingMenu = YES;
273edc8619eSBen Roth  __weak typeof(self) weakSelf = self;
274edc8619eSBen Roth  if (visible) {
275edc8619eSBen Roth    [_menuViewController willMoveToParentViewController:self];
2763980c593STomasz Sapeta
2773980c593STomasz Sapeta    if (_menuWindow == nil) {
2783980c593STomasz Sapeta      _menuWindow = [[EXMenuWindow alloc] init];
2793980c593STomasz Sapeta    }
2803980c593STomasz Sapeta
2813980c593STomasz Sapeta    [_menuWindow setFrame:self.view.frame];
2823980c593STomasz Sapeta    [_menuWindow addSubview:_menuViewController.view];
2833980c593STomasz Sapeta    [_menuWindow makeKeyAndVisible];
2843980c593STomasz Sapeta
285edc8619eSBen Roth    _menuViewController.view.alpha = 0.0f;
286edc8619eSBen Roth    _menuViewController.view.transform = CGAffineTransformMakeScale(1.1f, 1.1f);
287edc8619eSBen Roth    [UIView animateWithDuration:0.1f animations:^{
288d718b4b8SBen Roth      self.menuViewController.view.alpha = 1.0f;
289d718b4b8SBen Roth      self.menuViewController.view.transform = CGAffineTransformIdentity;
290edc8619eSBen Roth    } completion:^(BOOL finished) {
291edc8619eSBen Roth      __strong typeof(weakSelf) strongSelf = weakSelf;
292edc8619eSBen Roth      if (strongSelf) {
293edc8619eSBen Roth        strongSelf.isAnimatingMenu = NO;
294edc8619eSBen Roth        [strongSelf.menuViewController didMoveToParentViewController:self];
295edc8619eSBen Roth        if (completion) {
296edc8619eSBen Roth          completion();
297edc8619eSBen Roth        }
298edc8619eSBen Roth      }
299edc8619eSBen Roth    }];
300edc8619eSBen Roth  } else {
301edc8619eSBen Roth    _menuViewController.view.alpha = 1.0f;
302edc8619eSBen Roth    [UIView animateWithDuration:0.1f animations:^{
303d718b4b8SBen Roth      self.menuViewController.view.alpha = 0.0f;
304edc8619eSBen Roth    } completion:^(BOOL finished) {
305edc8619eSBen Roth      __strong typeof(weakSelf) strongSelf = weakSelf;
306edc8619eSBen Roth      if (strongSelf) {
307edc8619eSBen Roth        strongSelf.isAnimatingMenu = NO;
308edc8619eSBen Roth        [strongSelf.menuViewController willMoveToParentViewController:nil];
309edc8619eSBen Roth        [strongSelf.menuViewController.view removeFromSuperview];
310edc8619eSBen Roth        [strongSelf.menuViewController didMoveToParentViewController:nil];
311d718b4b8SBen Roth        strongSelf.menuWindow = nil;
312edc8619eSBen Roth        if (completion) {
313edc8619eSBen Roth          completion();
314edc8619eSBen Roth        }
315edc8619eSBen Roth      }
316edc8619eSBen Roth    }];
3177f6367d0SBen Roth  }
3187f6367d0SBen Roth}
3197f6367d0SBen Roth
320f44e163eSBen Roth- (EXHomeAppManager *)_getHomeAppManager
321f44e163eSBen Roth{
322f44e163eSBen Roth  return (EXHomeAppManager *)[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager;
323f44e163eSBen Roth}
324f44e163eSBen Roth
32566169681SBen Roth- (void)_maybeResetNuxState
32666169681SBen Roth{
32766169681SBen Roth  // used by appetize: optionally disable nux
32866169681SBen Roth  BOOL disableNuxDefaultsValue = [[NSUserDefaults standardUserDefaults] boolForKey:kEXHomeDisableNuxDefaultsKey];
32966169681SBen Roth  if (disableNuxDefaultsValue) {
33066169681SBen Roth    [self setIsNuxFinished:YES];
33166169681SBen Roth    [[NSUserDefaults standardUserDefaults] removeObjectForKey:kEXHomeDisableNuxDefaultsKey];
33266169681SBen Roth  }
33366169681SBen Roth}
33466169681SBen Roth
3354e9980b6SBen Roth- (void)_updateMenuButtonBehavior
336b0932293SBen Roth{
3374e9980b6SBen Roth  BOOL shouldShowButton = [[EXKernelDevKeyCommands sharedInstance] isLegacyMenuButtonAvailable];
338b0932293SBen Roth  dispatch_async(dispatch_get_main_queue(), ^{
339d718b4b8SBen Roth    self.btnMenu.hidden = !shouldShowButton;
340b0932293SBen Roth  });
341b0932293SBen Roth}
342b0932293SBen Roth
3434e9980b6SBen Roth- (void)_onMenuGestureRecognized:(EXMenuGestureRecognizer *)sender
3444e9980b6SBen Roth{
3454e9980b6SBen Roth  if (sender.state == UIGestureRecognizerStateEnded) {
3464e9980b6SBen Roth    [[EXKernel sharedInstance] switchTasks];
3474e9980b6SBen Roth  }
3484e9980b6SBen Roth}
3494e9980b6SBen Roth
3507f6367d0SBen Roth@end
3517f6367d0SBen Roth
3527f6367d0SBen RothNS_ASSUME_NONNULL_END
353