@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,23 @@
|
|
|
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 = "NebulaHost"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package.dig("repository", "url")
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
s.platforms = { :ios => "15.1" }
|
|
13
|
+
s.source = { :git => package.dig("repository", "url"), :tag => s.version.to_s }
|
|
14
|
+
|
|
15
|
+
s.source_files = "ios/Nebula/**/*.{swift,m,h}"
|
|
16
|
+
s.requires_arc = true
|
|
17
|
+
s.swift_version = "5.0"
|
|
18
|
+
|
|
19
|
+
s.dependency "React-Core"
|
|
20
|
+
s.dependency "React-RCTAppDelegate"
|
|
21
|
+
s.dependency "ReactAppDependencyProvider"
|
|
22
|
+
s.dependency "ZIPFoundation"
|
|
23
|
+
end
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
apply plugin: "com.android.library"
|
|
2
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
3
|
+
|
|
4
|
+
android {
|
|
5
|
+
namespace "com.hectorzhuang.nebula.host"
|
|
6
|
+
compileSdkVersion rootProject.ext.compileSdkVersion
|
|
7
|
+
|
|
8
|
+
defaultConfig {
|
|
9
|
+
minSdkVersion rootProject.ext.minSdkVersion
|
|
10
|
+
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
11
|
+
consumerProguardFiles "consumer-rules.pro"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
compileOptions {
|
|
15
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
16
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
kotlinOptions {
|
|
20
|
+
jvmTarget = "17"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
dependencies {
|
|
25
|
+
implementation("com.facebook.react:react-android")
|
|
26
|
+
implementation("androidx.appcompat:appcompat:1.7.1")
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Consumer rules placeholder for @nebula-rn/host.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest package="com.hectorzhuang.nebula.host" />
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
package com.hectorzhuang.nebula
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.os.Bundle
|
|
7
|
+
import android.view.View
|
|
8
|
+
import android.view.ViewTreeObserver
|
|
9
|
+
import android.widget.FrameLayout
|
|
10
|
+
import android.widget.LinearLayout
|
|
11
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
12
|
+
import androidx.appcompat.widget.Toolbar
|
|
13
|
+
import androidx.core.view.ViewCompat
|
|
14
|
+
import androidx.core.view.WindowInsetsCompat
|
|
15
|
+
import androidx.core.view.updatePadding
|
|
16
|
+
import com.facebook.react.interfaces.fabric.ReactSurface
|
|
17
|
+
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
|
|
18
|
+
import java.util.UUID
|
|
19
|
+
|
|
20
|
+
class NebulaActivity : AppCompatActivity(), DefaultHardwareBackBtnHandler {
|
|
21
|
+
companion object {
|
|
22
|
+
private const val EXTRA_APP_ID = "nebula.app_id"
|
|
23
|
+
private const val EXTRA_ROUTE_PATH = "nebula.route_path"
|
|
24
|
+
private const val EXTRA_ROUTE_URL = "nebula.route_url"
|
|
25
|
+
private const val EXTRA_INITIAL_PROPS = "nebula.initial_props"
|
|
26
|
+
private const val EXTRA_INSTANCE_ID = "nebula.instance_id"
|
|
27
|
+
|
|
28
|
+
fun createIntent(
|
|
29
|
+
activity: Activity,
|
|
30
|
+
appId: String,
|
|
31
|
+
routePath: String,
|
|
32
|
+
routeUrl: String,
|
|
33
|
+
initialProps: Map<String, Any?>,
|
|
34
|
+
): Intent {
|
|
35
|
+
return Intent(activity, NebulaActivity::class.java).apply {
|
|
36
|
+
putExtra(EXTRA_APP_ID, appId)
|
|
37
|
+
putExtra(EXTRA_ROUTE_PATH, routePath)
|
|
38
|
+
putExtra(EXTRA_ROUTE_URL, routeUrl)
|
|
39
|
+
putExtra(EXTRA_INITIAL_PROPS, org.json.JSONObject(initialProps).toString())
|
|
40
|
+
putExtra(EXTRA_INSTANCE_ID, UUID.randomUUID().toString())
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
lateinit var appId: String
|
|
46
|
+
private set
|
|
47
|
+
|
|
48
|
+
private lateinit var instanceId: String
|
|
49
|
+
private lateinit var routePath: String
|
|
50
|
+
private lateinit var routeUrl: String
|
|
51
|
+
private lateinit var rootHost: FrameLayout
|
|
52
|
+
private lateinit var toolbar: Toolbar
|
|
53
|
+
private var reactSurface: ReactSurface? = null
|
|
54
|
+
private var currentPageStyle: Map<String, Any?> = emptyMap()
|
|
55
|
+
private var didSignalContentReady = false
|
|
56
|
+
private var reactContentCreated = false
|
|
57
|
+
|
|
58
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
59
|
+
super.onCreate(savedInstanceState)
|
|
60
|
+
appId = intent.getStringExtra(EXTRA_APP_ID) ?: error("Missing appId")
|
|
61
|
+
routePath = NebulaManifestManager.normalizeRoute(intent.getStringExtra(EXTRA_ROUTE_PATH))
|
|
62
|
+
routeUrl = intent.getStringExtra(EXTRA_ROUTE_URL) ?: "nebula://$appId$routePath"
|
|
63
|
+
instanceId = intent.getStringExtra(EXTRA_INSTANCE_ID) ?: UUID.randomUUID().toString()
|
|
64
|
+
|
|
65
|
+
NebulaManifestManager.loadManifest(appId, NebulaConfig.sandboxDir(appId))
|
|
66
|
+
|
|
67
|
+
// Toolbar sits at the top; its top padding will be adjusted for the status bar inset.
|
|
68
|
+
toolbar = Toolbar(this).apply {
|
|
69
|
+
layoutParams = LinearLayout.LayoutParams(
|
|
70
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
71
|
+
LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// rootHost fills the remaining space below the toolbar.
|
|
76
|
+
rootHost = FrameLayout(this).apply {
|
|
77
|
+
id = View.generateViewId()
|
|
78
|
+
layoutParams = LinearLayout.LayoutParams(
|
|
79
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
80
|
+
0,
|
|
81
|
+
1f,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// A vertical LinearLayout guarantees rootHost is always below the toolbar
|
|
86
|
+
// without any manual margin/padding calculation.
|
|
87
|
+
val contentLayout = LinearLayout(this).apply {
|
|
88
|
+
orientation = LinearLayout.VERTICAL
|
|
89
|
+
layoutParams = LinearLayout.LayoutParams(
|
|
90
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
91
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
92
|
+
)
|
|
93
|
+
addView(toolbar)
|
|
94
|
+
addView(rootHost)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setSupportActionBar(toolbar)
|
|
98
|
+
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
|
99
|
+
toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
|
|
100
|
+
|
|
101
|
+
setContentView(contentLayout)
|
|
102
|
+
|
|
103
|
+
// Apply status-bar inset as top padding on the toolbar so its background
|
|
104
|
+
// extends behind the status bar (edge-to-edge).
|
|
105
|
+
ViewCompat.setOnApplyWindowInsetsListener(toolbar) { v, insets ->
|
|
106
|
+
val statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top
|
|
107
|
+
v.updatePadding(top = statusBarHeight)
|
|
108
|
+
insets
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
NebulaRouter.register(this)
|
|
112
|
+
updatePageStyle(NebulaManifestManager.getPageConfig(appId, routePath))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override fun onResume() {
|
|
116
|
+
super.onResume()
|
|
117
|
+
NebulaAppManager.onHostResume(appId, this, this)
|
|
118
|
+
if (!reactContentCreated) {
|
|
119
|
+
reactContentCreated = true
|
|
120
|
+
createReactContent()
|
|
121
|
+
}
|
|
122
|
+
NebulaEventHub.publishPageLifecycle(
|
|
123
|
+
NebulaPageLifecycleEvent(appId, instanceId, routePath, "show"),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
override fun onPause() {
|
|
128
|
+
NebulaEventHub.publishPageLifecycle(
|
|
129
|
+
NebulaPageLifecycleEvent(appId, instanceId, routePath, "hide"),
|
|
130
|
+
)
|
|
131
|
+
NebulaAppManager.onHostPause(appId, this)
|
|
132
|
+
super.onPause()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
override fun onDestroy() {
|
|
136
|
+
NebulaEventHub.publishPageLifecycle(
|
|
137
|
+
NebulaPageLifecycleEvent(appId, instanceId, routePath, "unload"),
|
|
138
|
+
)
|
|
139
|
+
reactSurface?.stop()
|
|
140
|
+
reactSurface?.clear()
|
|
141
|
+
reactSurface = null
|
|
142
|
+
NebulaAppManager.onHostDestroy(appId, this)
|
|
143
|
+
NebulaRouter.unregister(this)
|
|
144
|
+
super.onDestroy()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
override fun invokeDefaultOnBackPressed() {
|
|
148
|
+
super.onBackPressed()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fun updatePageStyle(style: Map<String, Any?>) {
|
|
152
|
+
currentPageStyle = style
|
|
153
|
+
val backgroundColor =
|
|
154
|
+
parseColor(style["backgroundColor"] as? String)
|
|
155
|
+
?: parseColor(style["navigationBarBackgroundColor"] as? String)
|
|
156
|
+
?: Color.WHITE
|
|
157
|
+
rootHost.setBackgroundColor(backgroundColor)
|
|
158
|
+
|
|
159
|
+
val title = style["navigationBarTitleText"] as? String
|
|
160
|
+
supportActionBar?.title = title ?: appId
|
|
161
|
+
|
|
162
|
+
val navBackground = parseColor(style["navigationBarBackgroundColor"] as? String) ?: backgroundColor
|
|
163
|
+
supportActionBar?.setBackgroundDrawable(android.graphics.drawable.ColorDrawable(navBackground))
|
|
164
|
+
|
|
165
|
+
val textColor = parseColor(style["navigationBarTextColor"] as? String) ?: Color.BLACK
|
|
166
|
+
toolbar.setTitleTextColor(textColor)
|
|
167
|
+
toolbar.navigationIcon?.setTint(textColor)
|
|
168
|
+
|
|
169
|
+
val navigationStyle = (style["navigationStyle"] as? String)?.lowercase() ?: "default"
|
|
170
|
+
if (navigationStyle == "custom") {
|
|
171
|
+
supportActionBar?.hide()
|
|
172
|
+
} else {
|
|
173
|
+
supportActionBar?.show()
|
|
174
|
+
val shouldShowBackButton = !isEntryRoute()
|
|
175
|
+
supportActionBar?.setDisplayHomeAsUpEnabled(shouldShowBackButton)
|
|
176
|
+
supportActionBar?.setHomeButtonEnabled(shouldShowBackButton)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
override fun onSupportNavigateUp(): Boolean {
|
|
181
|
+
onBackPressedDispatcher.onBackPressed()
|
|
182
|
+
return true
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private fun isEntryRoute(): Boolean {
|
|
186
|
+
val entryRoute = NebulaManifestManager.normalizeRoute(
|
|
187
|
+
NebulaManifestManager.getEntryPagePath(appId),
|
|
188
|
+
)
|
|
189
|
+
return routePath == entryRoute
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private fun createReactContent() {
|
|
193
|
+
val moduleName =
|
|
194
|
+
NebulaManifestManager.getComponentName(appId, routePath)
|
|
195
|
+
?: NebulaManifestManager.getComponentName(appId, "/")
|
|
196
|
+
?: "NebulaApp"
|
|
197
|
+
val initialProps = createInitialProps()
|
|
198
|
+
reactSurface = NebulaAppManager.createSurface(appId, this, moduleName, initialProps)
|
|
199
|
+
reactSurface?.start()
|
|
200
|
+
rootHost.removeAllViews()
|
|
201
|
+
reactSurface?.view?.let { view ->
|
|
202
|
+
rootHost.addView(
|
|
203
|
+
view,
|
|
204
|
+
FrameLayout.LayoutParams(
|
|
205
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
206
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
registerFirstContentDrawListener(view)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private fun registerFirstContentDrawListener(view: View) {
|
|
214
|
+
val observer = view.viewTreeObserver
|
|
215
|
+
if (!observer.isAlive) {
|
|
216
|
+
handleMiniappContentReady()
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
observer.addOnPreDrawListener(
|
|
221
|
+
object : ViewTreeObserver.OnPreDrawListener {
|
|
222
|
+
override fun onPreDraw(): Boolean {
|
|
223
|
+
if (view.viewTreeObserver.isAlive) {
|
|
224
|
+
view.viewTreeObserver.removeOnPreDrawListener(this)
|
|
225
|
+
}
|
|
226
|
+
handleMiniappContentReady()
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private fun handleMiniappContentReady() {
|
|
234
|
+
if (didSignalContentReady) {
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
didSignalContentReady = true
|
|
238
|
+
NebulaEventHub.publishPageLifecycle(
|
|
239
|
+
NebulaPageLifecycleEvent(appId, instanceId, routePath, "ready"),
|
|
240
|
+
)
|
|
241
|
+
NebulaRouter.onMiniappContentReady(appId)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private fun createInitialProps(): Bundle {
|
|
245
|
+
val props = parseJsonMap(intent.getStringExtra(EXTRA_INITIAL_PROPS))
|
|
246
|
+
val pageStyle = NebulaManifestManager.getPageConfig(appId, routePath)
|
|
247
|
+
props["appId"] = appId
|
|
248
|
+
props["instanceId"] = instanceId
|
|
249
|
+
props["sandboxPath"] = NebulaConfig.sandboxDir(appId).absolutePath
|
|
250
|
+
props["__routePath"] = routePath
|
|
251
|
+
props["__routeUrl"] = routeUrl
|
|
252
|
+
props["__pageConfig"] = pageStyle
|
|
253
|
+
return props.toBundle()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private fun parseColor(color: String?): Int? {
|
|
257
|
+
if (color.isNullOrBlank()) {
|
|
258
|
+
return null
|
|
259
|
+
}
|
|
260
|
+
return runCatching { Color.parseColor(color) }.getOrNull()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private fun parseJsonMap(raw: String?): MutableMap<String, Any?> {
|
|
264
|
+
if (raw.isNullOrBlank()) {
|
|
265
|
+
return linkedMapOf()
|
|
266
|
+
}
|
|
267
|
+
return NebulaManifestManager.jsonObjectToMap(org.json.JSONObject(raw)).toMutableMap()
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private fun Map<String, Any?>.toBundle(): Bundle {
|
|
272
|
+
val bundle = Bundle()
|
|
273
|
+
forEach { (key, value) ->
|
|
274
|
+
when (value) {
|
|
275
|
+
null -> bundle.putString(key, null)
|
|
276
|
+
is String -> bundle.putString(key, value)
|
|
277
|
+
is Int -> bundle.putInt(key, value)
|
|
278
|
+
is Double -> bundle.putDouble(key, value)
|
|
279
|
+
is Boolean -> bundle.putBoolean(key, value)
|
|
280
|
+
is Float -> bundle.putFloat(key, value)
|
|
281
|
+
is Long -> bundle.putLong(key, value)
|
|
282
|
+
is Map<*, *> -> {
|
|
283
|
+
@Suppress("UNCHECKED_CAST")
|
|
284
|
+
bundle.putBundle(key, (value as Map<String, Any?>).toBundle())
|
|
285
|
+
}
|
|
286
|
+
else -> bundle.putString(key, value.toString())
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return bundle
|
|
290
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
package com.hectorzhuang.nebula
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Application
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
import com.facebook.react.ReactHost
|
|
7
|
+
import com.facebook.react.ReactInstanceEventListener
|
|
8
|
+
import com.facebook.react.ReactPackage
|
|
9
|
+
import com.facebook.react.defaults.DefaultComponentsRegistry
|
|
10
|
+
import com.facebook.react.defaults.DefaultReactHostDelegate
|
|
11
|
+
import com.facebook.react.defaults.DefaultTurboModuleManagerDelegate
|
|
12
|
+
import com.facebook.react.fabric.ComponentFactory
|
|
13
|
+
import com.facebook.react.interfaces.fabric.ReactSurface
|
|
14
|
+
import com.facebook.react.runtime.ReactHostImpl
|
|
15
|
+
import com.facebook.react.runtime.hermes.HermesInstance
|
|
16
|
+
import com.facebook.react.bridge.JSBundleLoader
|
|
17
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
18
|
+
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
19
|
+
|
|
20
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
21
|
+
object NebulaAppManager {
|
|
22
|
+
private lateinit var application: Application
|
|
23
|
+
private var delegate: NebulaHostDelegate? = null
|
|
24
|
+
private val hosts = ConcurrentHashMap<String, ReactHost>()
|
|
25
|
+
|
|
26
|
+
fun initialize(app: Application, delegate: NebulaHostDelegate? = null) {
|
|
27
|
+
application = app
|
|
28
|
+
this.delegate = delegate
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fun preload(appId: String) {
|
|
32
|
+
acquireHost(appId)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fun startHost(appId: String, onReady: (() -> Unit)? = null) {
|
|
36
|
+
val host = acquireHost(appId)
|
|
37
|
+
if (onReady == null) {
|
|
38
|
+
host.start()
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
if (host.currentReactContext != null) {
|
|
42
|
+
android.os.Handler(android.os.Looper.getMainLooper()).post(onReady)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
val listener = object : ReactInstanceEventListener {
|
|
46
|
+
override fun onReactContextInitialized(context: com.facebook.react.bridge.ReactContext) {
|
|
47
|
+
host.removeReactInstanceEventListener(this)
|
|
48
|
+
onReady()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
host.addReactInstanceEventListener(listener)
|
|
52
|
+
host.start()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fun invalidate(appId: String) {
|
|
56
|
+
hosts.remove(appId)?.let { host ->
|
|
57
|
+
if (host is ReactHostImpl) {
|
|
58
|
+
host.destroy("NebulaAppManager.invalidate($appId)", null)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fun invalidateAll() {
|
|
64
|
+
hosts.forEach { (_, host) ->
|
|
65
|
+
if (host is ReactHostImpl) {
|
|
66
|
+
host.destroy("NebulaAppManager.invalidateAll", null)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
hosts.clear()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fun acquireHost(appId: String): ReactHost {
|
|
73
|
+
hosts[appId]?.let { return it }
|
|
74
|
+
|
|
75
|
+
val installedApp =
|
|
76
|
+
NebulaConfig.getInstalledApp(appId)
|
|
77
|
+
?: error("Mini-app $appId is not installed")
|
|
78
|
+
val enableDevSupport =
|
|
79
|
+
NebulaHost.isDebugBuild() && NebulaConfig.isDevelopmentMode(installedApp)
|
|
80
|
+
val devServerHost = NebulaConfig.getDevServerHost(installedApp)
|
|
81
|
+
val jsMainModulePath =
|
|
82
|
+
NebulaConfig.getDevModulePath(installedApp) ?: "index"
|
|
83
|
+
|
|
84
|
+
val packages = (delegate?.createMiniAppPackages(application) ?: emptyList()).toMutableList()
|
|
85
|
+
packages.add(NebulaPackage())
|
|
86
|
+
|
|
87
|
+
val delegate =
|
|
88
|
+
DefaultReactHostDelegate(
|
|
89
|
+
jsMainModulePath = jsMainModulePath,
|
|
90
|
+
jsBundleLoader = JSBundleLoader.createFileLoader(installedApp.bundlePath),
|
|
91
|
+
reactPackages = packages.distinctBy { it.javaClass.name },
|
|
92
|
+
jsRuntimeFactory = HermesInstance(),
|
|
93
|
+
turboModuleManagerDelegateBuilder = DefaultTurboModuleManagerDelegate.Builder(),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
val componentFactory = ComponentFactory().also { DefaultComponentsRegistry.register(it) }
|
|
97
|
+
val host =
|
|
98
|
+
ReactHostImpl(
|
|
99
|
+
application,
|
|
100
|
+
delegate,
|
|
101
|
+
componentFactory,
|
|
102
|
+
enableDevSupport,
|
|
103
|
+
enableDevSupport,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if (enableDevSupport && !devServerHost.isNullOrBlank()) {
|
|
107
|
+
host.setBundleSource(devServerHost, jsMainModulePath)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
hosts[appId] = host
|
|
111
|
+
return host
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fun createSurface(appId: String, activity: Activity, moduleName: String, initialProps: Bundle): ReactSurface {
|
|
115
|
+
val host = acquireHost(appId)
|
|
116
|
+
return host.createSurface(activity, moduleName, initialProps)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fun onHostPause(appId: String, activity: Activity) {
|
|
120
|
+
hosts[appId]?.onHostPause(activity)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fun onHostResume(appId: String, activity: Activity) {
|
|
124
|
+
hosts[appId]?.onHostResume(activity)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fun onHostResume(appId: String, activity: Activity, backButtonHandler: com.facebook.react.modules.core.DefaultHardwareBackBtnHandler) {
|
|
128
|
+
hosts[appId]?.onHostResume(activity, backButtonHandler)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fun onHostDestroy(appId: String, activity: Activity) {
|
|
132
|
+
hosts[appId]?.onHostDestroy(activity)
|
|
133
|
+
}
|
|
134
|
+
}
|