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