@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.
Files changed (37) hide show
  1. package/NebulaHost.podspec +23 -0
  2. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  4. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  5. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  6. package/android/.gradle/8.9/gc.properties +0 -0
  7. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  8. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  9. package/android/.gradle/vcs-1/gc.properties +0 -0
  10. package/android/build.gradle +27 -0
  11. package/android/consumer-rules.pro +1 -0
  12. package/android/src/main/AndroidManifest.xml +1 -0
  13. package/android/src/main/java/com/hectorzhuang/nebula/NebulaActivity.kt +290 -0
  14. package/android/src/main/java/com/hectorzhuang/nebula/NebulaAppManager.kt +134 -0
  15. package/android/src/main/java/com/hectorzhuang/nebula/NebulaConfig.kt +324 -0
  16. package/android/src/main/java/com/hectorzhuang/nebula/NebulaEventHub.kt +49 -0
  17. package/android/src/main/java/com/hectorzhuang/nebula/NebulaHost.kt +145 -0
  18. package/android/src/main/java/com/hectorzhuang/nebula/NebulaHostModalActivity.kt +178 -0
  19. package/android/src/main/java/com/hectorzhuang/nebula/NebulaManifestManager.kt +130 -0
  20. package/android/src/main/java/com/hectorzhuang/nebula/NebulaNativeModule.kt +604 -0
  21. package/android/src/main/java/com/hectorzhuang/nebula/NebulaPackage.kt +16 -0
  22. package/android/src/main/java/com/hectorzhuang/nebula/NebulaRouter.kt +300 -0
  23. package/ios/Nebula/NebulaAppManager.swift +355 -0
  24. package/ios/Nebula/NebulaConfig.swift +549 -0
  25. package/ios/Nebula/NebulaContainerController.swift +580 -0
  26. package/ios/Nebula/NebulaDevLoading.swift +333 -0
  27. package/ios/Nebula/NebulaHost.swift +611 -0
  28. package/ios/Nebula/NebulaManifest.swift +214 -0
  29. package/ios/Nebula/NebulaNativeModule.swift +682 -0
  30. package/ios/Nebula/NebulaNativeModuleBridge.m +364 -0
  31. package/ios/Nebula/NebulaPerformanceMonitor.swift +46 -0
  32. package/ios/Nebula/NebulaRouter.swift +594 -0
  33. package/ios/Nebula/NebulaRouterBridge.m +19 -0
  34. package/ios/Nebula/RNInstanceViewController.swift +52 -0
  35. package/package.json +41 -0
  36. package/react-native.config.js +14 -0
  37. 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
+ }