1 // Copyright 2015-present 650 Industries. All rights reserved. 2 3 @objc(DevMenuInternalModule) 4 public class DevMenuInternalModule: NSObject, RCTBridgeModule { 5 public static func moduleName() -> String! { 6 return "ExpoDevMenuInternal" 7 } 8 9 // Module DevMenuInternalModule requires main queue setup since it overrides `constantsToExport`. 10 public static func requiresMainQueueSetup() -> Bool { 11 return true; 12 } 13 14 private static var fontsWereLoaded = false; 15 16 let manager: DevMenuManager 17 18 init(manager: DevMenuManager) { 19 self.manager = manager 20 } 21 22 // MARK: JavaScript API 23 24 @objc 25 func constantsToExport() -> [String : Any] { 26 #if targetEnvironment(simulator) 27 let doesDeviceSupportKeyCommands = true 28 #else 29 let doesDeviceSupportKeyCommands = false 30 #endif 31 return ["doesDeviceSupportKeyCommands": doesDeviceSupportKeyCommands] 32 } 33 34 @objc 35 func loadFontsAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 36 if (DevMenuInternalModule.fontsWereLoaded) { 37 resolve(nil); 38 return; 39 } 40 41 let fonts = ["MaterialCommunityIcons", "Ionicons"] 42 for font in fonts { 43 guard let path = DevMenuUtils.resourcesBundle()?.path(forResource: font, ofType: "ttf") else { 44 reject("ERR_DEVMENU_CANNOT_FIND_FONT", "Font file for '\(font)' doesn't exist.", nil); 45 return; 46 } 47 guard let data = FileManager.default.contents(atPath: path) else { 48 reject("ERR_DEVMENU_CANNOT_OPEN_FONT_FILE", "Could not open '\(path)'.", nil); 49 return; 50 } 51 52 guard let provider = CGDataProvider(data: data as CFData) else { 53 reject("ERR_DEVMENU_CANNOT_CREATE_FONT_PROVIDER", "Could not create font provider for '\(font)'.", nil); 54 return; 55 } 56 guard let cgFont = CGFont(provider) else { 57 reject("ERR_DEVMENU_CANNOT_CREATE_FONT", "Could not create font for '\(font)'.", nil); 58 return; 59 } 60 61 var error: Unmanaged<CFError>? 62 if !CTFontManagerRegisterGraphicsFont(cgFont, &error) { 63 reject("ERR_DEVMENU_CANNOT_ADD_FONT", "Could not create font from loaded data for '\(font)'. '\(error.debugDescription)'.", nil) 64 return 65 } 66 } 67 68 DevMenuInternalModule.fontsWereLoaded = true 69 resolve(nil) 70 } 71 72 @objc 73 func dispatchActionAsync(_ actionId: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 74 if actionId == nil { 75 return reject("ERR_DEVMENU_ACTION_FAILED", "Action ID not provided.", nil) 76 } 77 manager.dispatchAction(withId: actionId) 78 resolve(nil) 79 } 80 81 @objc 82 func hideMenu() { 83 manager.hideMenu() 84 } 85 86 @objc 87 func setOnboardingFinished(_ finished: Bool) { 88 DevMenuSettings.isOnboardingFinished = finished 89 } 90 91 @objc 92 func getSettingsAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 93 resolve(DevMenuSettings.serialize()) 94 } 95 96 @objc 97 func setSettingsAsync(_ dict: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 98 if let motionGestureEnabled = dict["motionGestureEnabled"] as? Bool { 99 DevMenuSettings.motionGestureEnabled = motionGestureEnabled 100 } 101 if let touchGestureEnabled = dict["touchGestureEnabled"] as? Bool { 102 DevMenuSettings.touchGestureEnabled = touchGestureEnabled 103 } 104 if let keyCommandsEnabled = dict["keyCommandsEnabled"] as? Bool { 105 DevMenuSettings.keyCommandsEnabled = keyCommandsEnabled 106 } 107 if let showsAtLaunch = dict["showsAtLaunch"] as? Bool { 108 DevMenuSettings.showsAtLaunch = showsAtLaunch 109 } 110 } 111 112 @objc 113 func openDevMenuFromReactNative() { 114 guard let rctDevMenu = manager.session?.bridge.devMenu else { 115 return 116 } 117 118 DispatchQueue.main.async { 119 rctDevMenu.show() 120 } 121 } 122 } 123