1 // Copyright 2015-present 650 Industries. All rights reserved. 2 3 let motionGestureEnabledKey = "EXDevMenuMotionGestureEnabled" 4 let touchGestureEnabledKey = "EXDevMenuTouchGestureEnabled" 5 let keyCommandsEnabledKey = "EXDevMenuKeyCommandsEnabled" 6 let showsAtLaunchKey = "EXDevMenuShowsAtLaunch" 7 let isOnboardingFinishedKey = "EXDevMenuIsOnboardingFinished" 8 9 @objc(DevMenuPreferences) 10 public class DevMenuPreferences: NSObject, RCTBridgeModule { 11 /** 12 Initializes dev menu preferences by registering user defaults 13 and applying some preferences to static classes like interceptors. 14 */ 15 static func setup() { 16 UserDefaults.standard.register(defaults: [ 17 motionGestureEnabledKey: true, 18 touchGestureEnabledKey: true, 19 keyCommandsEnabledKey: true, 20 showsAtLaunchKey: false, 21 isOnboardingFinishedKey: false 22 ]) 23 24 /** 25 We don't want to uninstall `DevMenuMotionInterceptor`, because otherwise, the app on shake gesture will bring up the dev-menu from the RN. 26 So we added `isEnabled` to disable it, but not uninstall. 27 */ 28 DevMenuMotionInterceptor.isInstalled = true 29 DevMenuMotionInterceptor.isEnabled = DevMenuPreferences.motionGestureEnabled 30 DevMenuTouchInterceptor.isInstalled = DevMenuPreferences.touchGestureEnabled 31 DevMenuKeyCommandsInterceptor.isInstalled = DevMenuPreferences.keyCommandsEnabled 32 } 33 34 /** 35 Whether to enable shake gesture. 36 */ 37 static var motionGestureEnabled: Bool { 38 get { 39 return boolForKey(motionGestureEnabledKey) 40 } 41 set { 42 setBool(newValue, forKey: motionGestureEnabledKey) 43 DevMenuMotionInterceptor.isEnabled = newValue 44 } 45 } 46 47 /** 48 Whether to enable three-finger long press gesture. 49 */ 50 static var touchGestureEnabled: Bool { 51 get { 52 return boolForKey(touchGestureEnabledKey) 53 } 54 set { 55 setBool(newValue, forKey: touchGestureEnabledKey) 56 DevMenuTouchInterceptor.isInstalled = newValue 57 } 58 } 59 60 /** 61 Whether to enable key commands. 62 */ 63 static var keyCommandsEnabled: Bool { 64 get { 65 return boolForKey(keyCommandsEnabledKey) 66 } 67 set { 68 setBool(newValue, forKey: keyCommandsEnabledKey) 69 DevMenuKeyCommandsInterceptor.isInstalled = newValue 70 } 71 } 72 73 /** 74 Whether to automatically show the dev menu once its delegate is set and the bridge is loaded. 75 */ 76 static var showsAtLaunch: Bool { 77 get { 78 return DevMenuTestInterceptorManager.interceptor?.shouldShowAtLaunch ?? boolForKey(showsAtLaunchKey) 79 } 80 set { 81 setBool(newValue, forKey: showsAtLaunchKey) 82 } 83 } 84 85 /** 86 Returns `true` only if the user finished onboarding, `false` otherwise. 87 This is now public because expo-dev-launcher needs access 88 in order to disable the onboarding popup for certain bundle URLs 89 */ 90 public static var isOnboardingFinished: Bool { 91 get { 92 return DevMenuTestInterceptorManager.interceptor?.isOnboardingFinishedKey ?? boolForKey(isOnboardingFinishedKey) 93 } 94 set { 95 setBool(newValue, forKey: isOnboardingFinishedKey) 96 } 97 } 98 99 /** 100 Serializes settings into a dictionary so they can be passed through the bridge. 101 */ 102 static func serialize() -> [String: Any] { 103 return [ 104 "motionGestureEnabled": DevMenuPreferences.motionGestureEnabled, 105 "touchGestureEnabled": DevMenuPreferences.touchGestureEnabled, 106 "keyCommandsEnabled": DevMenuPreferences.keyCommandsEnabled, 107 "showsAtLaunch": DevMenuPreferences.showsAtLaunch, 108 "isOnboardingFinished": DevMenuPreferences.isOnboardingFinished 109 ] 110 } 111 112 static func setSettings(_ settings: [String: Any]) { 113 if let motionGestureEnabled = settings["motionGestureEnabled"] as? Bool { 114 DevMenuPreferences.motionGestureEnabled = motionGestureEnabled 115 } 116 if let touchGestureEnabled = settings["touchGestureEnabled"] as? Bool { 117 DevMenuPreferences.touchGestureEnabled = touchGestureEnabled 118 } 119 if let keyCommandsEnabled = settings["keyCommandsEnabled"] as? Bool { 120 DevMenuPreferences.keyCommandsEnabled = keyCommandsEnabled 121 } 122 if let showsAtLaunch = settings["showsAtLaunch"] as? Bool { 123 DevMenuPreferences.showsAtLaunch = showsAtLaunch 124 } 125 } 126 127 // MARK - RCTBridgeModule 128 129 public static func moduleName() -> String! { 130 return "DevMenuPreferences" 131 } 132 133 public static func requiresMainQueueSetup() -> Bool { 134 return true 135 } 136 137 @objc 138 func getPreferencesAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 139 resolve(DevMenuPreferences.serialize()) 140 } 141 142 @objc 143 func setPreferencesAsync(_ settings: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 144 DevMenuPreferences.setSettings(settings) 145 resolve(nil) 146 } 147 } 148 149 150 151 private func boolForKey(_ key: String) -> Bool { 152 return UserDefaults.standard.bool(forKey: key) 153 } 154 155 private func setBool(_ value: Bool, forKey key: String) { 156 UserDefaults.standard.set(value, forKey: key) 157 } 158