@nebula-rn/host 0.0.1
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/NebulaHost.podspec +23 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +27 -0
- package/android/consumer-rules.pro +1 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaActivity.kt +290 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaAppManager.kt +134 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaConfig.kt +324 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaEventHub.kt +49 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaHost.kt +145 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaHostModalActivity.kt +178 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaManifestManager.kt +130 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaNativeModule.kt +604 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaPackage.kt +16 -0
- package/android/src/main/java/com/hectorzhuang/nebula/NebulaRouter.kt +300 -0
- package/ios/Nebula/NebulaAppManager.swift +355 -0
- package/ios/Nebula/NebulaConfig.swift +549 -0
- package/ios/Nebula/NebulaContainerController.swift +580 -0
- package/ios/Nebula/NebulaDevLoading.swift +333 -0
- package/ios/Nebula/NebulaHost.swift +611 -0
- package/ios/Nebula/NebulaManifest.swift +214 -0
- package/ios/Nebula/NebulaNativeModule.swift +682 -0
- package/ios/Nebula/NebulaNativeModuleBridge.m +364 -0
- package/ios/Nebula/NebulaPerformanceMonitor.swift +46 -0
- package/ios/Nebula/NebulaRouter.swift +594 -0
- package/ios/Nebula/NebulaRouterBridge.m +19 -0
- package/ios/Nebula/RNInstanceViewController.swift +52 -0
- package/package.json +41 -0
- package/react-native.config.js +14 -0
- package/src/index.ts +9 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
//
|
|
2
|
+
// NebulaDevLoading.swift
|
|
3
|
+
// SuperApp - Nebula Mini-App Container
|
|
4
|
+
//
|
|
5
|
+
// Dev loading configuration, default view, and presenter
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import SwiftUI
|
|
10
|
+
import UIKit
|
|
11
|
+
import QuartzCore
|
|
12
|
+
import React
|
|
13
|
+
import React_RCTAppDelegate
|
|
14
|
+
|
|
15
|
+
@objc public protocol NebulaAppDelegate: NSObjectProtocol {
|
|
16
|
+
@objc optional func nebulaNavigationController(
|
|
17
|
+
_ host: NebulaHost
|
|
18
|
+
) -> UINavigationController?
|
|
19
|
+
|
|
20
|
+
@objc optional func nebulaRootViewFactory(
|
|
21
|
+
_ host: NebulaHost
|
|
22
|
+
) -> RCTRootViewFactory?
|
|
23
|
+
|
|
24
|
+
@objc optional func nebulaHost(
|
|
25
|
+
_ host: NebulaHost,
|
|
26
|
+
makeHostViewController moduleName: String,
|
|
27
|
+
initialProperties: [AnyHashable: Any]?,
|
|
28
|
+
title: String?
|
|
29
|
+
) -> UIViewController?
|
|
30
|
+
|
|
31
|
+
@objc optional func nebulaHost(
|
|
32
|
+
_ host: NebulaHost,
|
|
33
|
+
configureDevLoading configuration: NebulaDevLoadingConfiguration
|
|
34
|
+
) -> NebulaDevLoadingConfiguration
|
|
35
|
+
|
|
36
|
+
@objc optional func nebulaHost(
|
|
37
|
+
_ host: NebulaHost,
|
|
38
|
+
makeDevLoadingView configuration: NebulaDevLoadingConfiguration
|
|
39
|
+
) -> NebulaDevLoadingView
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@objcMembers public final class NebulaDevLoadingConfiguration: NSObject, NSCopying {
|
|
43
|
+
public var title: String
|
|
44
|
+
public var message: String
|
|
45
|
+
public var progress: NSNumber?
|
|
46
|
+
public var accentColor: UIColor
|
|
47
|
+
public var backgroundColor: UIColor
|
|
48
|
+
public var titleColor: UIColor
|
|
49
|
+
public var messageColor: UIColor
|
|
50
|
+
public var showsActivityIndicator: Bool
|
|
51
|
+
public var showsDismissHint: Bool
|
|
52
|
+
public var cornerRadius: CGFloat
|
|
53
|
+
public var maximumWidth: CGFloat
|
|
54
|
+
public var contentInsets: UIEdgeInsets
|
|
55
|
+
|
|
56
|
+
public init(
|
|
57
|
+
title: String,
|
|
58
|
+
message: String,
|
|
59
|
+
progress: NSNumber? = nil,
|
|
60
|
+
accentColor: UIColor = UIColor(red: 0.10, green: 0.47, blue: 0.95, alpha: 1.0),
|
|
61
|
+
backgroundColor: UIColor = .systemBackground,
|
|
62
|
+
titleColor: UIColor = .label,
|
|
63
|
+
messageColor: UIColor = .secondaryLabel,
|
|
64
|
+
showsActivityIndicator: Bool = true,
|
|
65
|
+
showsDismissHint: Bool = false,
|
|
66
|
+
cornerRadius: CGFloat = 18,
|
|
67
|
+
maximumWidth: CGFloat = 320,
|
|
68
|
+
contentInsets: UIEdgeInsets = UIEdgeInsets(top: 14, left: 14, bottom: 14, right: 14)
|
|
69
|
+
) {
|
|
70
|
+
self.title = title
|
|
71
|
+
self.message = message
|
|
72
|
+
self.progress = progress
|
|
73
|
+
self.accentColor = accentColor
|
|
74
|
+
self.backgroundColor = backgroundColor
|
|
75
|
+
self.titleColor = titleColor
|
|
76
|
+
self.messageColor = messageColor
|
|
77
|
+
self.showsActivityIndicator = showsActivityIndicator
|
|
78
|
+
self.showsDismissHint = showsDismissHint
|
|
79
|
+
self.cornerRadius = cornerRadius
|
|
80
|
+
self.maximumWidth = maximumWidth
|
|
81
|
+
self.contentInsets = contentInsets
|
|
82
|
+
super.init()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public func copy(with zone: NSZone? = nil) -> Any {
|
|
86
|
+
NebulaDevLoadingConfiguration(
|
|
87
|
+
title: title,
|
|
88
|
+
message: message,
|
|
89
|
+
progress: progress,
|
|
90
|
+
accentColor: accentColor,
|
|
91
|
+
backgroundColor: backgroundColor,
|
|
92
|
+
titleColor: titleColor,
|
|
93
|
+
messageColor: messageColor,
|
|
94
|
+
showsActivityIndicator: showsActivityIndicator,
|
|
95
|
+
showsDismissHint: showsDismissHint,
|
|
96
|
+
cornerRadius: cornerRadius,
|
|
97
|
+
maximumWidth: maximumWidth,
|
|
98
|
+
contentInsets: contentInsets
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@objcMembers public class NebulaDevLoadingView: UIView {
|
|
104
|
+
public func render(with configuration: NebulaDevLoadingConfiguration) {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@objcMembers public final class NebulaDefaultDevLoadingView: NebulaDevLoadingView {
|
|
109
|
+
private let hostingController = UIHostingController(
|
|
110
|
+
rootView: NebulaDefaultDevLoadingContentView(configuration: .init(title: "", message: ""))
|
|
111
|
+
)
|
|
112
|
+
private var maximumWidthConstraint: NSLayoutConstraint?
|
|
113
|
+
|
|
114
|
+
public override init(frame: CGRect) {
|
|
115
|
+
super.init(frame: frame)
|
|
116
|
+
translatesAutoresizingMaskIntoConstraints = false
|
|
117
|
+
alpha = 0
|
|
118
|
+
transform = CGAffineTransform(translationX: 0, y: -10)
|
|
119
|
+
|
|
120
|
+
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
121
|
+
hostingController.view.backgroundColor = .clear
|
|
122
|
+
addSubview(hostingController.view)
|
|
123
|
+
NSLayoutConstraint.activate([
|
|
124
|
+
hostingController.view.topAnchor.constraint(equalTo: topAnchor),
|
|
125
|
+
hostingController.view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
126
|
+
hostingController.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
127
|
+
hostingController.view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
128
|
+
])
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public required init?(coder: NSCoder) {
|
|
132
|
+
fatalError("init(coder:) has not been implemented")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public override func render(with configuration: NebulaDevLoadingConfiguration) {
|
|
136
|
+
backgroundColor = .clear
|
|
137
|
+
hostingController.rootView = NebulaDefaultDevLoadingContentView(
|
|
138
|
+
configuration: configuration
|
|
139
|
+
)
|
|
140
|
+
maximumWidthConstraint?.isActive = false
|
|
141
|
+
maximumWidthConstraint = widthAnchor.constraint(lessThanOrEqualToConstant: configuration.maximumWidth)
|
|
142
|
+
maximumWidthConstraint?.isActive = true
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private struct NebulaDefaultDevLoadingContentView: View {
|
|
147
|
+
let configuration: NebulaDevLoadingConfiguration
|
|
148
|
+
|
|
149
|
+
var body: some View {
|
|
150
|
+
VStack(spacing: 0) {
|
|
151
|
+
HStack(alignment: .top, spacing: 10) {
|
|
152
|
+
if configuration.showsActivityIndicator {
|
|
153
|
+
ProgressView()
|
|
154
|
+
.progressViewStyle(.circular)
|
|
155
|
+
.tint(Color(configuration.accentColor))
|
|
156
|
+
.padding(.top, 2)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
160
|
+
Text(configuration.title)
|
|
161
|
+
.font(.system(size: 13, weight: .semibold))
|
|
162
|
+
.foregroundStyle(Color(configuration.titleColor))
|
|
163
|
+
|
|
164
|
+
Text(configuration.message)
|
|
165
|
+
.font(.system(size: 11, weight: .medium, design: .monospaced))
|
|
166
|
+
.foregroundStyle(Color(configuration.messageColor))
|
|
167
|
+
.lineLimit(2)
|
|
168
|
+
|
|
169
|
+
if configuration.showsDismissHint {
|
|
170
|
+
Text("Tap to dismiss")
|
|
171
|
+
.font(.system(size: 10, weight: .semibold))
|
|
172
|
+
.foregroundStyle(Color(configuration.titleColor).opacity(0.72))
|
|
173
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
174
|
+
.padding(.top, 4)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if let progress = configuration.progress {
|
|
178
|
+
ProgressView(value: progress.doubleValue)
|
|
179
|
+
.tint(Color(configuration.accentColor))
|
|
180
|
+
.padding(.top, 10)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
.padding(EdgeInsets(configuration.contentInsets))
|
|
186
|
+
.background(
|
|
187
|
+
ZStack {
|
|
188
|
+
RoundedRectangle(cornerRadius: configuration.cornerRadius, style: .continuous)
|
|
189
|
+
.fill(Color(UIColor.label.withAlphaComponent(0.08)))
|
|
190
|
+
RoundedRectangle(cornerRadius: configuration.cornerRadius, style: .continuous)
|
|
191
|
+
.fill(.ultraThinMaterial)
|
|
192
|
+
RoundedRectangle(cornerRadius: configuration.cornerRadius, style: .continuous)
|
|
193
|
+
.fill(Color(configuration.backgroundColor).opacity(0.82))
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
.compositingGroup()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private extension EdgeInsets {
|
|
201
|
+
init(_ insets: UIEdgeInsets) {
|
|
202
|
+
self.init(
|
|
203
|
+
top: insets.top,
|
|
204
|
+
leading: insets.left,
|
|
205
|
+
bottom: insets.bottom,
|
|
206
|
+
trailing: insets.right
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@objcMembers public final class NebulaDevLoadingPresenter: NSObject {
|
|
212
|
+
public static let shared = NebulaDevLoadingPresenter()
|
|
213
|
+
|
|
214
|
+
private var window: UIWindow?
|
|
215
|
+
private weak var loadingView: NebulaDevLoadingView?
|
|
216
|
+
private var showTimestamp: CFTimeInterval?
|
|
217
|
+
private var isHiding = false
|
|
218
|
+
|
|
219
|
+
public func show(
|
|
220
|
+
title: String,
|
|
221
|
+
message: String,
|
|
222
|
+
progress: NSNumber?,
|
|
223
|
+
titleColor: UIColor? = nil,
|
|
224
|
+
messageColor: UIColor? = nil,
|
|
225
|
+
backgroundColor: UIColor? = nil,
|
|
226
|
+
dismissButton: Bool = false
|
|
227
|
+
) {
|
|
228
|
+
let configuration = NebulaHost.shared.resolveDevLoadingConfiguration(
|
|
229
|
+
title: title,
|
|
230
|
+
message: message,
|
|
231
|
+
progress: progress,
|
|
232
|
+
titleColor: titleColor,
|
|
233
|
+
messageColor: messageColor,
|
|
234
|
+
backgroundColor: backgroundColor,
|
|
235
|
+
dismissButton: dismissButton
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
DispatchQueue.main.async {
|
|
239
|
+
self.present(configuration: configuration)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public func hide() {
|
|
244
|
+
DispatchQueue.main.async {
|
|
245
|
+
guard let window = self.window,
|
|
246
|
+
let loadingView = self.loadingView,
|
|
247
|
+
!self.isHiding else {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
self.isHiding = true
|
|
252
|
+
let presentedTime = self.showTimestamp.map { CACurrentMediaTime() - $0 } ?? 0
|
|
253
|
+
let delay = max(0, 0.45 - presentedTime)
|
|
254
|
+
|
|
255
|
+
UIView.animate(withDuration: 0.22, delay: delay, options: [.curveEaseInOut]) {
|
|
256
|
+
loadingView.alpha = 0
|
|
257
|
+
loadingView.transform = CGAffineTransform(translationX: 0, y: -16)
|
|
258
|
+
} completion: { _ in
|
|
259
|
+
window.isHidden = true
|
|
260
|
+
self.window = nil
|
|
261
|
+
self.loadingView = nil
|
|
262
|
+
self.showTimestamp = nil
|
|
263
|
+
self.isHiding = false
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private func present(configuration: NebulaDevLoadingConfiguration) {
|
|
269
|
+
guard !ProcessInfo.processInfo.environment.keys.contains("XCTestConfigurationFilePath") else {
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
guard let mainWindow = UIApplication.shared.connectedScenes
|
|
273
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
274
|
+
.flatMap(\.windows)
|
|
275
|
+
.first(where: \.isKeyWindow) else {
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let window = self.window ?? {
|
|
280
|
+
let overlayWindow: UIWindow
|
|
281
|
+
if let windowScene = mainWindow.windowScene {
|
|
282
|
+
overlayWindow = UIWindow(windowScene: windowScene)
|
|
283
|
+
} else {
|
|
284
|
+
overlayWindow = UIWindow(frame: UIScreen.main.bounds)
|
|
285
|
+
}
|
|
286
|
+
overlayWindow.windowLevel = .statusBar + 1
|
|
287
|
+
let rootViewController = UIViewController()
|
|
288
|
+
rootViewController.view.backgroundColor = .clear
|
|
289
|
+
overlayWindow.rootViewController = rootViewController
|
|
290
|
+
self.window = overlayWindow
|
|
291
|
+
return overlayWindow
|
|
292
|
+
}()
|
|
293
|
+
|
|
294
|
+
let contentView: NebulaDevLoadingView
|
|
295
|
+
if let existingLoadingView = loadingView {
|
|
296
|
+
contentView = existingLoadingView
|
|
297
|
+
contentView.render(with: configuration)
|
|
298
|
+
} else {
|
|
299
|
+
contentView = NebulaHost.shared.makeDevLoadingView(configuration: configuration)
|
|
300
|
+
contentView.translatesAutoresizingMaskIntoConstraints = false
|
|
301
|
+
window.rootViewController?.view.addSubview(contentView)
|
|
302
|
+
NSLayoutConstraint.activate([
|
|
303
|
+
contentView.topAnchor.constraint(
|
|
304
|
+
equalTo: window.rootViewController!.view.topAnchor,
|
|
305
|
+
constant: mainWindow.safeAreaInsets.top + 10
|
|
306
|
+
),
|
|
307
|
+
contentView.centerXAnchor.constraint(equalTo: window.rootViewController!.view.centerXAnchor),
|
|
308
|
+
contentView.leadingAnchor.constraint(
|
|
309
|
+
greaterThanOrEqualTo: window.rootViewController!.view.leadingAnchor,
|
|
310
|
+
constant: 12
|
|
311
|
+
),
|
|
312
|
+
contentView.trailingAnchor.constraint(
|
|
313
|
+
lessThanOrEqualTo: window.rootViewController!.view.trailingAnchor,
|
|
314
|
+
constant: -12
|
|
315
|
+
),
|
|
316
|
+
])
|
|
317
|
+
loadingView = contentView
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
window.isHidden = false
|
|
321
|
+
window.layoutIfNeeded()
|
|
322
|
+
|
|
323
|
+
if showTimestamp == nil {
|
|
324
|
+
showTimestamp = CACurrentMediaTime()
|
|
325
|
+
}
|
|
326
|
+
isHiding = false
|
|
327
|
+
|
|
328
|
+
UIView.animate(withDuration: 0.18, delay: 0, options: [.curveEaseOut]) {
|
|
329
|
+
contentView.alpha = 1
|
|
330
|
+
contentView.transform = .identity
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|