@anythingai/launcher 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @anything/launcher
2
+
3
+ My new module
4
+
5
+ # API documentation
6
+
7
+ - [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/@anything/launcher/)
8
+ - [Documentation for the main branch](https://docs.expo.dev/versions/unversioned/sdk/@anything/launcher/)
9
+
10
+ # Installation in managed Expo projects
11
+
12
+ For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no documentation available then this library is not yet usable within managed projects — it is likely to be included in an upcoming Expo SDK release.
13
+
14
+ # Installation in bare React Native projects
15
+
16
+ For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
17
+
18
+ ### Add the package to your npm dependencies
19
+
20
+ ```
21
+ npm install @anything/launcher
22
+ ```
23
+
24
+ ### Configure for Android
25
+
26
+
27
+
28
+
29
+ ### Configure for iOS
30
+
31
+ Run `npx pod-install` after installing the npm package.
32
+
33
+ # Contributing
34
+
35
+ Contributions are very welcome! Please refer to guidelines described in the [contributing guide]( https://github.com/expo/expo#contributing).
@@ -0,0 +1,12 @@
1
+ import { NativeModule } from "expo-modules-core";
2
+ declare class AnythingLauncherModule extends NativeModule {
3
+ open(url: string, showCloseButton: boolean, showProgress: boolean, isWeb: boolean): void;
4
+ reset(): void;
5
+ reload(): void;
6
+ getAppLauncherURL(): string | null;
7
+ setAppLauncherURL(url: string | null): void;
8
+ isWeb(): boolean;
9
+ }
10
+ declare const anythingLauncherModule: AnythingLauncherModule | null;
11
+ export default anythingLauncherModule;
12
+ //# sourceMappingURL=AnythingLauncherModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnythingLauncherModule.d.ts","sourceRoot":"","sources":["../src/AnythingLauncherModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,OAAO,sBAAuB,SAAQ,YAAY;IACvD,IAAI,CACF,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,OAAO,EACxB,YAAY,EAAE,OAAO,EACrB,KAAK,EAAE,OAAO,GACb,IAAI;IACP,KAAK,IAAI,IAAI;IACb,MAAM,IAAI,IAAI;IACd,iBAAiB,IAAI,MAAM,GAAG,IAAI;IAClC,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAC3C,KAAK,IAAI,OAAO;CACjB;AAID,QAAA,MAAM,sBAAsB,+BAE3B,CAAA;AAED,eAAe,sBAAsB,CAAA"}
@@ -0,0 +1,5 @@
1
+ import { requireNativeModule } from "expo-modules-core";
2
+ const isExpoGo = globalThis.expo?.modules?.ExpoGo;
3
+ const anythingLauncherModule = isExpoGo ? null : requireNativeModule("AnythingLauncherModule");
4
+ export default anythingLauncherModule;
5
+ //# sourceMappingURL=AnythingLauncherModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnythingLauncherModule.js","sourceRoot":"","sources":["../src/AnythingLauncherModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAgBtE,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAA;AAEjD,MAAM,sBAAsB,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAClE,wBAAwB,CACzB,CAAA;AAED,eAAe,sBAAsB,CAAA","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo-modules-core\";\n\ndeclare class AnythingLauncherModule extends NativeModule {\n open(\n url: string,\n showCloseButton: boolean,\n showProgress: boolean,\n isWeb: boolean\n ): void;\n reset(): void;\n reload(): void;\n getAppLauncherURL(): string | null;\n setAppLauncherURL(url: string | null): void;\n isWeb(): boolean;\n}\n\nconst isExpoGo = globalThis.expo?.modules?.ExpoGo\n\nconst anythingLauncherModule = isExpoGo ? null : requireNativeModule<AnythingLauncherModule>(\n \"AnythingLauncherModule\"\n)\n\nexport default anythingLauncherModule\n"]}
@@ -0,0 +1,9 @@
1
+ import { NativeModule } from "expo-modules-core";
2
+ declare class AnythingLauncherReloadOverlayModule extends NativeModule {
3
+ show(showCloseButton: boolean, showProgress: boolean): void;
4
+ hide(): void;
5
+ update(status: string, ratio: number): void;
6
+ }
7
+ declare const anythingLauncherReloadOverlayModule: AnythingLauncherReloadOverlayModule | null;
8
+ export default anythingLauncherReloadOverlayModule;
9
+ //# sourceMappingURL=AnythingLauncherReloadOverlayModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnythingLauncherReloadOverlayModule.d.ts","sourceRoot":"","sources":["../src/AnythingLauncherReloadOverlayModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,OAAO,mCAAoC,SAAQ,YAAY;IACpE,IAAI,CAAC,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,GAAG,IAAI;IAC3D,IAAI,IAAI,IAAI;IACZ,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;CAC5C;AAID,QAAA,MAAM,mCAAmC,4CAExC,CAAA;AAED,eAAe,mCAAmC,CAAA"}
@@ -0,0 +1,5 @@
1
+ import { requireNativeModule } from "expo-modules-core";
2
+ const isExpoGo = globalThis.expo?.modules?.ExpoGo;
3
+ const anythingLauncherReloadOverlayModule = isExpoGo ? null : requireNativeModule("AnythingLauncherReloadOverlayModule");
4
+ export default anythingLauncherReloadOverlayModule;
5
+ //# sourceMappingURL=AnythingLauncherReloadOverlayModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnythingLauncherReloadOverlayModule.js","sourceRoot":"","sources":["../src/AnythingLauncherReloadOverlayModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAQtE,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAA;AAEjD,MAAM,mCAAmC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAC/E,qCAAqC,CACtC,CAAA;AAED,eAAe,mCAAmC,CAAA","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo-modules-core\";\n\ndeclare class AnythingLauncherReloadOverlayModule extends NativeModule {\n show(showCloseButton: boolean, showProgress: boolean): void;\n hide(): void;\n update(status: string, ratio: number): void;\n}\n\nconst isExpoGo = globalThis.expo?.modules?.ExpoGo\n\nconst anythingLauncherReloadOverlayModule = isExpoGo ? null : requireNativeModule<AnythingLauncherReloadOverlayModule>(\n \"AnythingLauncherReloadOverlayModule\",\n)\n\nexport default anythingLauncherReloadOverlayModule\n"]}
@@ -0,0 +1,4 @@
1
+ import AnythingLauncherModule from './AnythingLauncherModule';
2
+ import AnythingLauncherReloadOverlayModule from './AnythingLauncherReloadOverlayModule';
3
+ export { AnythingLauncherModule, AnythingLauncherReloadOverlayModule };
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,MAAM,0BAA0B,CAAC;AAC9D,OAAO,mCAAmC,MAAM,uCAAuC,CAAC;AACxF,OAAO,EAAE,sBAAsB,EAAE,mCAAmC,EAAE,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import AnythingLauncherModule from './AnythingLauncherModule';
2
+ import AnythingLauncherReloadOverlayModule from './AnythingLauncherReloadOverlayModule';
3
+ export { AnythingLauncherModule, AnythingLauncherReloadOverlayModule };
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,MAAM,0BAA0B,CAAC;AAC9D,OAAO,mCAAmC,MAAM,uCAAuC,CAAC;AACxF,OAAO,EAAE,sBAAsB,EAAE,mCAAmC,EAAE,CAAC","sourcesContent":["import AnythingLauncherModule from './AnythingLauncherModule';\nimport AnythingLauncherReloadOverlayModule from './AnythingLauncherReloadOverlayModule';\nexport { AnythingLauncherModule, AnythingLauncherReloadOverlayModule };"]}
@@ -0,0 +1,19 @@
1
+ {
2
+ "platforms": [
3
+ "apple",
4
+ "android",
5
+ "web"
6
+ ],
7
+ "apple": {
8
+ "modules": [
9
+ "AnythingLauncherModule",
10
+ "AnythingLauncherReloadOverlayModule"
11
+ ]
12
+ },
13
+ "android": {
14
+ "modules": [
15
+ "expo.modules.anythinglauncher.AnythingLauncherModule",
16
+ "expo.modules.anythinglauncher.AnythingLauncherReloadOverlayModule"
17
+ ]
18
+ }
19
+ }
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'AnythingLauncher'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = {
14
+ :ios => '15.1',
15
+ :tvos => '15.1'
16
+ }
17
+ s.swift_version = '5.4'
18
+ s.source = { git: 'https://github.com/Create-Inc/anything-mobile-monorepo' }
19
+ s.static_framework = true
20
+
21
+ s.dependency 'ExpoModulesCore'
22
+
23
+ # Swift/Objective-C compatibility
24
+ s.pod_target_xcconfig = {
25
+ 'DEFINES_MODULE' => 'YES',
26
+ }
27
+
28
+ s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
29
+ end
@@ -0,0 +1,105 @@
1
+ // AnythingLauncherModule.swift
2
+ import ExpoModulesCore
3
+ import React
4
+ import UIKit
5
+
6
+ private let kBundleOverrideKey = "anything.bundleOverrideURL" // same key
7
+ private let kIsWebOverrideKey = "anything.bundleOverrideIsWeb"
8
+
9
+ public class AnythingLauncherModule: Module {
10
+ private var observersInstalled = false
11
+
12
+ public func definition() -> ModuleDefinition {
13
+ Name("AnythingLauncherModule")
14
+
15
+ OnCreate { self.installObserversIfNeeded() }
16
+
17
+ Function("open") { (url: String, showCloseButton: Bool, showProgress: Bool, isWeb: Bool) in
18
+ guard URL(string: url) != nil else {
19
+ throw GenericException("Invalid URL: \(url)")
20
+ }
21
+
22
+ UserDefaults.standard.set(url, forKey: kBundleOverrideKey)
23
+ UserDefaults.standard.set(isWeb, forKey: kIsWebOverrideKey)
24
+
25
+ DispatchQueue.main.async {
26
+ ReloadOverlay.show(showCloseButton: showCloseButton, showProgress: showProgress)
27
+ RCTTriggerReloadCommandListeners("AnythingLauncher.open(\(url))")
28
+ }
29
+ }
30
+
31
+ Function("reset") { () in
32
+ UserDefaults.standard.removeObject(forKey: kBundleOverrideKey)
33
+ UserDefaults.standard.removeObject(forKey: kIsWebOverrideKey)
34
+
35
+ DispatchQueue.main.async {
36
+ RCTTriggerReloadCommandListeners("AnythingLauncher.reset()")
37
+ }
38
+ }
39
+
40
+ Function("reload") {
41
+ DispatchQueue.main.async {
42
+ ReloadOverlay.show(showCloseButton: false, showProgress: false)
43
+ RCTTriggerReloadCommandListeners("AnythingLauncher.reload()")
44
+ }
45
+ }
46
+
47
+ Function("getAppLauncherURL") { () -> String? in
48
+ return UserDefaults.standard.string(forKey: "anything.appLauncherURL")
49
+ }
50
+
51
+ Function("setAppLauncherURL") { (url: String?) in
52
+ if let url = url {
53
+ UserDefaults.standard.set(url, forKey: "anything.appLauncherURL")
54
+ } else {
55
+ UserDefaults.standard.removeObject(forKey: "anything.appLauncherURL")
56
+ }
57
+ }
58
+
59
+ Function("isWeb") { () -> Bool in
60
+ return UserDefaults.standard.bool(forKey: kIsWebOverrideKey)
61
+ }
62
+ }
63
+
64
+ private func installObserversIfNeeded() {
65
+ guard !observersInstalled else { return }
66
+ observersInstalled = true
67
+ let nc = NotificationCenter.default
68
+
69
+ nc.addObserver(forName: NSNotification.Name("RCTJavaScriptDidLoadNotification"),
70
+ object: nil, queue: .main) { _ in
71
+ DispatchQueue.main.async {
72
+ ReloadOverlay.hide()
73
+ }
74
+ }
75
+ nc.addObserver(forName: NSNotification.Name("RCTContentDidAppearNotification"),
76
+ object: nil, queue: .main) { _ in
77
+ DispatchQueue.main.async {
78
+ ReloadOverlay.hide()
79
+ }
80
+ }
81
+ nc.addObserver(forName: NSNotification.Name("RCTJavaScriptDidFailToLoadNotification"),
82
+ object: nil, queue: .main) { _ in
83
+ UserDefaults.standard.removeObject(forKey: kBundleOverrideKey)
84
+ DispatchQueue.main.async {
85
+ ReloadOverlay.hide()
86
+ }
87
+ }
88
+
89
+ nc.addObserver(forName: NSNotification.Name("ALJavaScriptLoaderProgress"),
90
+ object: nil, queue: .main) { note in
91
+ let status = note.userInfo?["status"] as? String
92
+ let ratio = note.userInfo?["ratio"] as? Double
93
+ DispatchQueue.main.async {
94
+ ReloadOverlay.update(status: status ?? "Loading…", ratio: ratio)
95
+ }
96
+ }
97
+
98
+ nc.addObserver(forName: NSNotification.Name("ALJavaScriptLoaderComplete"),
99
+ object: nil, queue: .main) { _ in
100
+ DispatchQueue.main.async {
101
+ ReloadOverlay.hide()
102
+ }
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,201 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+ import React
4
+
5
+ private let kBundleOverrideKey = "anything.bundleOverrideURL" // same key
6
+ private let kIsWebOverrideKey = "anything.bundleOverrideIsWeb"
7
+
8
+ // Pure UI helper. Not a Module.
9
+ final class ReloadOverlay {
10
+ static var window: UIWindow?
11
+
12
+ // Keep handles so we can update them
13
+ private static weak var progressLabel: UILabel?
14
+ private static weak var progressBar: UIProgressView?
15
+
16
+ private static let closeHandler = CloseHandler()
17
+
18
+ @MainActor
19
+ static func show(showCloseButton: Bool, showProgress: Bool) {
20
+ guard window == nil else { return }
21
+
22
+ let overlayWindow: UIWindow
23
+ if #available(iOS 13.0, *) {
24
+ let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
25
+ if let activeScene = scenes.first(where: { $0.activationState == .foregroundActive }) ?? scenes.first {
26
+ overlayWindow = UIWindow(windowScene: activeScene)
27
+ overlayWindow.frame = activeScene.coordinateSpace.bounds
28
+ } else {
29
+ overlayWindow = UIWindow(frame: UIScreen.main.bounds)
30
+ }
31
+ overlayWindow.overrideUserInterfaceStyle = .unspecified
32
+ } else {
33
+ overlayWindow = UIWindow(frame: UIScreen.main.bounds)
34
+ }
35
+
36
+ overlayWindow.windowLevel = .statusBar + 1
37
+ overlayWindow.backgroundColor = .clear
38
+
39
+ let vc = UIViewController()
40
+ vc.view.backgroundColor = .systemBackground
41
+ let guide = vc.view.safeAreaLayoutGuide
42
+
43
+ // Splash image
44
+ let imageView = UIImageView(image: UIImage(named: "splash-icon"))
45
+ imageView.translatesAutoresizingMaskIntoConstraints = false
46
+ imageView.contentMode = .scaleAspectFit
47
+ vc.view.addSubview(imageView)
48
+
49
+ var constraints: [NSLayoutConstraint] = [
50
+ imageView.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor),
51
+ imageView.centerYAnchor.constraint(equalTo: guide.centerYAnchor, constant: -20),
52
+ imageView.widthAnchor.constraint(equalToConstant: 72),
53
+ imageView.heightAnchor.constraint(equalToConstant: 75),
54
+ imageView.topAnchor.constraint(greaterThanOrEqualTo: guide.topAnchor, constant: 24)
55
+ ]
56
+
57
+ var label: UILabel?
58
+ var bar: UIProgressView?
59
+
60
+ if showProgress {
61
+ // Progress label
62
+ let l = UILabel()
63
+ l.translatesAutoresizingMaskIntoConstraints = false
64
+ l.font = .systemFont(ofSize: 13, weight: .regular)
65
+ l.textColor = .secondaryLabel
66
+ l.textAlignment = .center
67
+ l.text = "Preparing…"
68
+ vc.view.addSubview(l)
69
+
70
+ // Progress bar
71
+ let b = UIProgressView(progressViewStyle: .default)
72
+ b.translatesAutoresizingMaskIntoConstraints = false
73
+ b.progress = 0
74
+ b.progressTintColor = .label
75
+ b.trackTintColor = .tertiarySystemFill
76
+ b.transform = b.transform.scaledBy(x: 1, y: 2.0)
77
+ vc.view.addSubview(b)
78
+
79
+ // Pin to bottom safe area
80
+ constraints.append(contentsOf: [
81
+ b.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 24),
82
+ b.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -24),
83
+ b.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: -24),
84
+
85
+ l.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 24),
86
+ l.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -24),
87
+ l.bottomAnchor.constraint(equalTo: b.topAnchor, constant: -8),
88
+ ])
89
+
90
+ label = l
91
+ bar = b
92
+ }
93
+
94
+ NSLayoutConstraint.activate(constraints)
95
+
96
+ // Optional close button
97
+ if showCloseButton {
98
+ let closeButton = UIButton(type: .system)
99
+ closeButton.translatesAutoresizingMaskIntoConstraints = false
100
+ closeButton.backgroundColor = .clear
101
+ if let img = UIImage(systemName: "xmark") {
102
+ closeButton.setImage(img, for: .normal)
103
+ closeButton.tintColor = .label
104
+ } else {
105
+ closeButton.setTitle("×", for: .normal)
106
+ closeButton.setTitleColor(.label, for: .normal)
107
+ closeButton.titleLabel?.font = .systemFont(ofSize: 20, weight: .semibold)
108
+ }
109
+ closeButton.accessibilityLabel = "Exit reload"
110
+ closeButton.addTarget(closeHandler, action: #selector(CloseHandler.onTap), for: .touchUpInside)
111
+ vc.view.addSubview(closeButton)
112
+
113
+ NSLayoutConstraint.activate([
114
+ closeButton.topAnchor.constraint(equalTo: guide.topAnchor, constant: 8),
115
+ closeButton.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -8),
116
+ closeButton.heightAnchor.constraint(equalToConstant: 32),
117
+ closeButton.widthAnchor.constraint(equalToConstant: 32)
118
+ ])
119
+ }
120
+
121
+ overlayWindow.rootViewController = vc
122
+ overlayWindow.isHidden = false
123
+ overlayWindow.makeKeyAndVisible()
124
+ window = overlayWindow
125
+
126
+ // store refs
127
+ progressLabel = label
128
+ progressBar = bar
129
+
130
+ if showProgress {
131
+ update(status: "Preparing…", ratio: nil)
132
+ }
133
+ }
134
+
135
+ @MainActor
136
+ static func update(status: String?, ratio: Double?) {
137
+ if let label = progressLabel, let status = status {
138
+ if let r = ratio {
139
+ let pct = Int((max(0.0, min(1.0, r))) * 100.0)
140
+ label.text = "\(status) \(pct)%"
141
+ } else {
142
+ label.text = status
143
+ }
144
+ }
145
+ if let r = ratio, let bar = progressBar {
146
+ bar.progress = Float(max(0.0, min(1.0, r)))
147
+ }
148
+ }
149
+
150
+ @MainActor
151
+ static func hide() {
152
+ guard let w = window else { return }
153
+ // Add slight delay to avoid flashing
154
+ Task { @MainActor in
155
+ // try? await Task.sleep(nanoseconds: 300_000_000) // 300ms
156
+ guard window === w else { return } // don't hide if another overlay was shown
157
+ w.isHidden = true
158
+ window = nil
159
+ progressLabel = nil
160
+ progressBar = nil
161
+ }
162
+ }
163
+
164
+ private final class CloseHandler: NSObject {
165
+ @objc func onTap() {
166
+ // Haptic feedback on close
167
+ let generator = UIImpactFeedbackGenerator(style: .medium)
168
+ generator.impactOccurred()
169
+ UserDefaults.standard.removeObject(forKey: kBundleOverrideKey)
170
+ UserDefaults.standard.removeObject(forKey: kIsWebOverrideKey)
171
+ DispatchQueue.main.async {
172
+ ReloadOverlay.hide()
173
+ RCTTriggerReloadCommandListeners("AnythingLauncher.reset()")
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ public final class AnythingLauncherReloadOverlayModule: Module {
180
+ public func definition() -> ModuleDefinition {
181
+ Name("AnythingLauncherReloadOverlayModule")
182
+
183
+ Function("show") { (showCloseButton: Bool, showProgress: Bool) -> Void in
184
+ DispatchQueue.main.async {
185
+ ReloadOverlay.show(showCloseButton: showCloseButton, showProgress: showProgress)
186
+ }
187
+ }
188
+
189
+ Function("update") { (status: String?, ratio: Double?) -> Void in
190
+ DispatchQueue.main.async {
191
+ ReloadOverlay.update(status: status, ratio: ratio)
192
+ }
193
+ }
194
+
195
+ Function("hide") { () -> Void in
196
+ DispatchQueue.main.async {
197
+ ReloadOverlay.hide()
198
+ }
199
+ }
200
+ }
201
+ }
@@ -0,0 +1,76 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <objc/runtime.h>
3
+ #import <React/RCTJavaScriptLoader.h>
4
+
5
+ // Notification names you can observe from Swift.
6
+ // (You can also just use the raw strings in Swift and skip these if you like.)
7
+ NSString *const ALJavaScriptLoaderProgressNotification = @"ALJavaScriptLoaderProgress";
8
+ NSString *const ALJavaScriptLoaderCompleteNotification = @"ALJavaScriptLoaderComplete";
9
+
10
+ @implementation RCTJavaScriptLoader (ALProgress)
11
+
12
+ + (void)load
13
+ {
14
+ static dispatch_once_t onceToken;
15
+ dispatch_once(&onceToken, ^{
16
+ // Swizzle the class method: +loadBundleAtURL:onProgress:onComplete:
17
+ Class meta = object_getClass((id)self);
18
+ SEL originalSEL = @selector(loadBundleAtURL:onProgress:onComplete:);
19
+ SEL swizzledSEL = @selector(al_loadBundleAtURL:onProgress:onComplete:);
20
+
21
+ Method original = class_getClassMethod(self, originalSEL);
22
+ Method swizzled = class_getClassMethod(self, swizzledSEL);
23
+
24
+ if (original && swizzled) {
25
+ method_exchangeImplementations(original, swizzled);
26
+ }
27
+ });
28
+ }
29
+
30
+ + (void)al_loadBundleAtURL:(NSURL *)scriptURL
31
+ onProgress:(RCTSourceLoadProgressBlock)onProgress
32
+ onComplete:(RCTSourceLoadBlock)onComplete
33
+ {
34
+ // Wrap progress: forward to original callback and also post a notification.
35
+ RCTSourceLoadProgressBlock wrappedProgress = ^(RCTLoadingProgress *progress) {
36
+ if (progress) {
37
+ NSNumber *done = progress.done ?: @(0);
38
+ NSNumber *total = progress.total ?: @(0);
39
+
40
+ double ratio = 0.0;
41
+ if (total.integerValue > 0) {
42
+ ratio = (double)done.integerValue / (double)total.integerValue;
43
+ if (ratio < 0.0) ratio = 0.0;
44
+ if (ratio > 1.0) ratio = 1.0;
45
+ }
46
+
47
+ [[NSNotificationCenter defaultCenter] postNotificationName:ALJavaScriptLoaderProgressNotification
48
+ object:nil
49
+ userInfo:@{
50
+ @"status": progress.status ?: @"",
51
+ @"done": done,
52
+ @"total": total,
53
+ @"ratio": @(ratio)
54
+ }];
55
+ }
56
+
57
+ if (onProgress) { onProgress(progress); }
58
+ };
59
+
60
+ // Wrap completion: DO NOT touch private RCTSource ivars.
61
+ RCTSourceLoadBlock wrappedComplete = ^(NSError *error, RCTSource *source) {
62
+ [[NSNotificationCenter defaultCenter] postNotificationName:ALJavaScriptLoaderCompleteNotification
63
+ object:nil
64
+ userInfo:@{
65
+ @"error": error ?: (id)kCFNull,
66
+ // Useful for debugging what you *asked* RN to load:
67
+ @"requestedURL": scriptURL.absoluteString ?: @""
68
+ }];
69
+ if (onComplete) { onComplete(error, source); }
70
+ };
71
+
72
+ // Call the original implementation (now swapped).
73
+ [self al_loadBundleAtURL:scriptURL onProgress:wrappedProgress onComplete:wrappedComplete];
74
+ }
75
+
76
+ @end
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@anythingai/launcher",
3
+ "version": "0.1.7",
4
+ "description": "My new module",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo lint --quiet",
11
+ "test": "expo-module test",
12
+ "prepare": "expo-module prepare",
13
+ "prepublishOnly": "expo-module prepublishOnly",
14
+ "expo-module": "expo-module",
15
+ "open:ios": "xed example/ios",
16
+ "open:android": "open -a \"Android Studio\" example/android"
17
+ },
18
+ "keywords": [
19
+ "react-native",
20
+ "expo",
21
+ "@anything/launcher",
22
+ "AnythingLauncher"
23
+ ],
24
+ "repository": "https://github.com/Create-Inc/anything-mobile-monorepo",
25
+ "bugs": {
26
+ "url": "https://github.com/Create-Inc/anything-mobile-monorepo/issues"
27
+ },
28
+ "author": "Zobeir Hamid <zobeirhamid@berkeley.edu> (zobeirhamid)",
29
+ "license": "MIT",
30
+ "homepage": "https://github.com/Create-Inc/anything-mobile-monorepo#readme",
31
+ "dependencies": {},
32
+ "devDependencies": {
33
+ "@types/invariant": "^2.2.37",
34
+ "@types/react": "~19.0.0",
35
+ "expo": "^54.0.0",
36
+ "expo-module-scripts": "^4.1.10",
37
+ "react-native": "0.81.4"
38
+ },
39
+ "peerDependencies": {
40
+ "expo": "*",
41
+ "react": "*",
42
+ "react-native": "*"
43
+ },
44
+ "publishConfig": {
45
+ "access": "restricted"
46
+ }
47
+ }