@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 +35 -0
- package/build/AnythingLauncherModule.d.ts +12 -0
- package/build/AnythingLauncherModule.d.ts.map +1 -0
- package/build/AnythingLauncherModule.js +5 -0
- package/build/AnythingLauncherModule.js.map +1 -0
- package/build/AnythingLauncherReloadOverlayModule.d.ts +9 -0
- package/build/AnythingLauncherReloadOverlayModule.d.ts.map +1 -0
- package/build/AnythingLauncherReloadOverlayModule.js +5 -0
- package/build/AnythingLauncherReloadOverlayModule.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +19 -0
- package/ios/AnythingLauncher.podspec +29 -0
- package/ios/AnythingLauncherModule.swift +105 -0
- package/ios/AnythingLauncherReloadOverlayModule.swift +201 -0
- package/ios/RCTJavaScriptLoader+ALProgress.m +76 -0
- package/package.json +47 -0
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"]}
|
package/build/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|