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