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