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