@anythingai/launcher 0.1.8 → 0.1.9

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.
@@ -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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anythingai/launcher",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "My new module",
5
5
  "main": "build/index.js",
6
6
  "module": "build/index.mjs",