1 // Copyright 2015-present 650 Industries. All rights reserved. 2 import SafariServices 3 4 @objc(DevMenuInternalModule) 5 public class DevMenuInternalModule: NSObject, RCTBridgeModule { 6 public static func moduleName() -> String! { 7 return "ExpoDevMenuInternal" 8 } 9 10 // Module DevMenuInternalModule requires main queue setup since it overrides `constantsToExport`. 11 public static func requiresMainQueueSetup() -> Bool { 12 return true 13 } 14 15 let manager: DevMenuManager 16 17 public override init() { 18 self.manager = DevMenuManager.shared 19 } 20 21 init(manager: DevMenuManager) { 22 self.manager = manager 23 } 24 25 // MARK: JavaScript API 26 27 @objc 28 public func constantsToExport() -> [AnyHashable: Any] { 29 #if targetEnvironment(simulator) 30 let doesDeviceSupportKeyCommands = true 31 #else 32 let doesDeviceSupportKeyCommands = false 33 #endif 34 return ["doesDeviceSupportKeyCommands": doesDeviceSupportKeyCommands] 35 } 36 37 @objc 38 func fetchDataSourceAsync(_ dataSourceId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 39 guard let dataSourceId = dataSourceId else { 40 return reject("ERR_DEVMENU_DATA_SOURCE_FAILED", "DataSource ID not provided.", nil) 41 } 42 43 for dataSource in manager.devMenuDataSources { 44 if dataSource.id == dataSourceId { 45 dataSource.fetchData { data in 46 resolve(data.map { $0.serialize() }) 47 } 48 return 49 } 50 } 51 52 return reject("ERR_DEVMENU_DATA_SOURCE_FAILED", "DataSource \(dataSourceId) not founded.", nil) 53 } 54 55 @objc 56 func dispatchCallableAsync(_ callableId: String?, args: [String: Any]?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 57 guard let callableId = callableId else { 58 return reject("ERR_DEVMENU_ACTION_FAILED", "Callable ID not provided.", nil) 59 } 60 manager.dispatchCallable(withId: callableId, args: args) 61 resolve(nil) 62 } 63 64 @objc 65 func hideMenu() { 66 manager.hideMenu() 67 } 68 69 @objc 70 func setOnboardingFinished(_ finished: Bool) { 71 DevMenuSettings.isOnboardingFinished = finished 72 } 73 74 @objc 75 func getSettingsAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 76 resolve(DevMenuSettings.serialize()) 77 } 78 79 @objc 80 func setSettingsAsync(_ dict: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 81 if let motionGestureEnabled = dict["motionGestureEnabled"] as? Bool { 82 DevMenuSettings.motionGestureEnabled = motionGestureEnabled 83 } 84 if let touchGestureEnabled = dict["touchGestureEnabled"] as? Bool { 85 DevMenuSettings.touchGestureEnabled = touchGestureEnabled 86 } 87 if let keyCommandsEnabled = dict["keyCommandsEnabled"] as? Bool { 88 DevMenuSettings.keyCommandsEnabled = keyCommandsEnabled 89 } 90 if let showsAtLaunch = dict["showsAtLaunch"] as? Bool { 91 DevMenuSettings.showsAtLaunch = showsAtLaunch 92 } 93 } 94 95 @objc 96 func openDevMenuFromReactNative() { 97 guard let rctDevMenu = manager.currentBridge?.devMenu else { 98 return 99 } 100 101 DispatchQueue.main.async { 102 rctDevMenu.show() 103 } 104 } 105 106 @objc 107 func onScreenChangeAsync(_ currentScreen: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 108 manager.setCurrentScreen(currentScreen) 109 resolve(nil) 110 } 111 } 112