xref: /expo/ios/Client/Menu/EXDevMenuManager.m (revision f67462bc)
1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXDevMenuManager.h"
4#import "EXDevMenuWindow.h"
5#import "EXHomeModule.h"
6#import "EXDevMenuMotionInterceptor.h"
7#import "EXDevMenuGestureInterceptor.h"
8
9static NSString *kEXDevMenuMotionGestureEnabled = @"EXDevMenuMotionGestureEnabled";
10static NSString *kEXDevMenuTouchGestureEnabled = @"EXDevMenuTouchGestureEnabled";
11
12@interface EXDevMenuManager ()
13
14@property (nonatomic, strong) EXDevMenuWindow *window;
15
16@end
17
18@implementation EXDevMenuManager
19
20+ (instancetype)sharedInstance
21{
22  static EXDevMenuManager *manager;
23  static dispatch_once_t once;
24  dispatch_once(&once, ^{
25    if (!manager) {
26      manager = [EXDevMenuManager new];
27
28      // Read initial attributes from user defaults.
29      id motionGestureEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:kEXDevMenuMotionGestureEnabled];
30      id touchGestureEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:kEXDevMenuTouchGestureEnabled];
31      manager.interceptMotionGesture = motionGestureEnabled != nil ? [motionGestureEnabled boolValue] : YES;
32      manager.interceptTouchGesture = touchGestureEnabled != nil ? [touchGestureEnabled boolValue] : YES;
33    }
34  });
35  return manager;
36}
37
38#pragma mark - API
39
40- (BOOL)interceptMotionGesture
41{
42  return [EXDevMenuMotionInterceptor isInstalled];
43}
44
45- (void)setInterceptMotionGesture:(BOOL)interceptMotionGesture
46{
47  interceptMotionGesture ? [EXDevMenuMotionInterceptor install] : [EXDevMenuMotionInterceptor uninstall];
48  [[NSUserDefaults standardUserDefaults] setBool:interceptMotionGesture forKey:kEXDevMenuMotionGestureEnabled];
49}
50
51- (BOOL)interceptTouchGesture
52{
53  return [EXDevMenuGestureInterceptor isInstalled];
54}
55
56- (void)setInterceptTouchGesture:(BOOL)interceptTouchGesture
57{
58  interceptTouchGesture ? [EXDevMenuGestureInterceptor install] : [EXDevMenuGestureInterceptor uninstall];
59  [[NSUserDefaults standardUserDefaults] setBool:interceptTouchGesture forKey:kEXDevMenuTouchGestureEnabled];
60}
61
62- (RCTBridge *)mainBridge
63{
64  return [_delegate mainBridgeForDevMenuManager:self];
65}
66
67- (BOOL)isVisible
68{
69  return _window ? !_window.hidden : NO;
70}
71
72- (BOOL)open
73{
74  if (![self canChangeVisibility:YES]) {
75    return NO;
76  }
77  [self setVisibility:YES];
78  return YES;
79}
80
81- (BOOL)close
82{
83  if (![self canChangeVisibility:NO]) {
84    return NO;
85  }
86  EXHomeModule *homeModule = [[self mainBridge] moduleForName:@"ExponentKernel"];
87
88  if (homeModule) {
89    // This will trigger `closeWithoutAnimation` once the animation is finished.
90    [homeModule requestToCloseDevMenu];
91  } else {
92    // Module not found, close immediately?
93    [self closeWithoutAnimation];
94  }
95
96  return YES;
97}
98
99- (BOOL)toggle
100{
101  return self.isVisible ? [self close] : [self open];
102}
103
104- (void)closeWithoutAnimation
105{
106  [self setVisibility:NO];
107}
108
109#pragma mark - delegate stubs
110
111- (BOOL)canChangeVisibility:(BOOL)visible
112{
113  if (self.isVisible == visible) {
114    return NO;
115  }
116  if ([_delegate respondsToSelector:@selector(devMenuManager:canChangeVisibility:)]) {
117    return [_delegate devMenuManager:self canChangeVisibility:visible];
118  }
119  return YES;
120}
121
122#pragma mark - internal
123
124- (void)setVisibility:(BOOL)visible
125{
126  dispatch_async(dispatch_get_main_queue(), ^{
127    if (!self->_window) {
128      self->_window = [EXDevMenuWindow new];
129    }
130    if (visible) {
131      [self->_window makeKeyAndVisible];
132    } else {
133      self->_window.hidden = YES;
134    }
135  });
136}
137
138@end
139