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    */
88   static var isOnboardingFinished: Bool {
89     get {
90       return DevMenuTestInterceptorManager.interceptor?.isOnboardingFinishedKey ?? boolForKey(isOnboardingFinishedKey)
91     }
92     set {
93       setBool(newValue, forKey: isOnboardingFinishedKey)
94     }
95   }
96 
97   /**
98    Serializes settings into a dictionary so they can be passed through the bridge.
99    */
100   static func serialize() -> [String: Any] {
101     return [
102       "motionGestureEnabled": DevMenuPreferences.motionGestureEnabled,
103       "touchGestureEnabled": DevMenuPreferences.touchGestureEnabled,
104       "keyCommandsEnabled": DevMenuPreferences.keyCommandsEnabled,
105       "showsAtLaunch": DevMenuPreferences.showsAtLaunch,
106       "isOnboardingFinished": DevMenuPreferences.isOnboardingFinished
107     ]
108   }
109 
110   static func setSettings(_ settings: [String: Any]) {
111     if let motionGestureEnabled = settings["motionGestureEnabled"] as? Bool {
112       DevMenuPreferences.motionGestureEnabled = motionGestureEnabled
113     }
114     if let touchGestureEnabled = settings["touchGestureEnabled"] as? Bool {
115       DevMenuPreferences.touchGestureEnabled = touchGestureEnabled
116     }
117     if let keyCommandsEnabled = settings["keyCommandsEnabled"] as? Bool {
118       DevMenuPreferences.keyCommandsEnabled = keyCommandsEnabled
119     }
120     if let showsAtLaunch = settings["showsAtLaunch"] as? Bool {
121       DevMenuPreferences.showsAtLaunch = showsAtLaunch
122     }
123   }
124 
125   // MARK - RCTBridgeModule
126 
127   public static func moduleName() -> String! {
128     return "DevMenuPreferences"
129   }
130 
131   public static func requiresMainQueueSetup() -> Bool {
132     return true
133   }
134 
135   @objc
136   func getPreferencesAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
137     resolve(DevMenuPreferences.serialize())
138   }
139 
140   @objc
141   func setPreferencesAsync(_ settings: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
142     DevMenuPreferences.setSettings(settings)
143     resolve(nil)
144   }
145 }
146 
147 
148 
149 private func boolForKey(_ key: String) -> Bool {
150   return UserDefaults.standard.bool(forKey: key)
151 }
152 
153 private func setBool(_ value: Bool, forKey key: String) {
154   UserDefaults.standard.set(value, forKey: key)
155 }
156