@react-native-native/nativ-fabric 0.1.0
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/NativFabric.podspec +41 -0
- package/android/build.gradle +128 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/CMakeLists.txt +59 -0
- package/android/src/main/cpp/NativBindingsInstaller.cpp +393 -0
- package/android/src/main/cpp/NativRuntime.cpp +508 -0
- package/android/src/main/java/com/nativfabric/ComposeHost.kt +26 -0
- package/android/src/main/java/com/nativfabric/NativContainerPackage.kt +35 -0
- package/android/src/main/java/com/nativfabric/NativContainerView.kt +51 -0
- package/android/src/main/java/com/nativfabric/NativContainerViewManager.kt +62 -0
- package/android/src/main/java/com/nativfabric/NativRuntime.kt +201 -0
- package/android/src/main/java/com/nativfabric/NativRuntimeModule.kt +37 -0
- package/android/src/main/java/com/nativfabric/compose/ComposeWrappers.kt +45 -0
- package/app.plugin.js +159 -0
- package/expo-module.config.json +6 -0
- package/ios/NativContainerComponentView.mm +137 -0
- package/ios/NativRuntime.h +36 -0
- package/ios/NativRuntime.mm +549 -0
- package/metro/Nativ.h +126 -0
- package/metro/compilers/android-compiler.js +339 -0
- package/metro/compilers/dylib-compiler.js +474 -0
- package/metro/compilers/kotlin-compiler.js +632 -0
- package/metro/compilers/rust-compiler.js +722 -0
- package/metro/compilers/static-compiler.js +1118 -0
- package/metro/compilers/swift-compiler.js +363 -0
- package/metro/extractors/cpp-ast-extractor.js +126 -0
- package/metro/extractors/kotlin-extractor.js +125 -0
- package/metro/extractors/rust-extractor.js +118 -0
- package/metro/index.js +236 -0
- package/metro/transformer.js +649 -0
- package/metro/utils/bridge-generator.js +50 -0
- package/metro/utils/compile-commands.js +104 -0
- package/metro/utils/cpp-daemon.js +344 -0
- package/metro/utils/dts-generator.js +32 -0
- package/metro/utils/include-resolver.js +73 -0
- package/metro/utils/kotlin-daemon.js +394 -0
- package/metro/utils/type-mapper.js +63 -0
- package/package.json +43 -0
- package/react-native.config.js +13 -0
- package/src/NativContainerNativeComponent.ts +9 -0
- package/src/NativeNativRuntime.ts +8 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
// NativRuntime JNI bridge — Android equivalent of iOS NativRuntime.
|
|
2
|
+
// Maintains the render registry + sync function registry.
|
|
3
|
+
// User .so dylibs register via __attribute__((constructor)) at dlopen time.
|
|
4
|
+
|
|
5
|
+
#include <jni.h>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <unordered_map>
|
|
8
|
+
#include <unordered_set>
|
|
9
|
+
#include <android/log.h>
|
|
10
|
+
|
|
11
|
+
#define LOG_TAG "NativRuntime"
|
|
12
|
+
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
|
13
|
+
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
|
14
|
+
|
|
15
|
+
// ─── Registry types (same C ABI as iOS) ────────────────────────────────
|
|
16
|
+
|
|
17
|
+
typedef const char* (*NativSyncFn)(const char* argsJson);
|
|
18
|
+
typedef void (*NativAsyncFn)(const char* argsJson, void (*resolve)(const char*), void (*reject)(const char*, const char*));
|
|
19
|
+
typedef void (*NativRenderFn)(void* view_handle, float width, float height,
|
|
20
|
+
void* runtime, void* props);
|
|
21
|
+
|
|
22
|
+
// ─── Registries ────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
static std::unordered_map<std::string, NativSyncFn>& getSyncRegistry() {
|
|
25
|
+
static std::unordered_map<std::string, NativSyncFn> reg;
|
|
26
|
+
return reg;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static std::unordered_map<std::string, NativAsyncFn>& getAsyncRegistry() {
|
|
30
|
+
static std::unordered_map<std::string, NativAsyncFn> reg;
|
|
31
|
+
return reg;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static std::unordered_map<std::string, NativRenderFn>& getRenderRegistry() {
|
|
35
|
+
static std::unordered_map<std::string, NativRenderFn> reg;
|
|
36
|
+
return reg;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Props snapshot (passed to render functions) ───────────────────────
|
|
40
|
+
|
|
41
|
+
struct PropsSnapshot {
|
|
42
|
+
std::unordered_map<std::string, std::string> strings;
|
|
43
|
+
std::unordered_map<std::string, double> numbers;
|
|
44
|
+
std::unordered_map<std::string, bool> bools;
|
|
45
|
+
std::unordered_set<std::string> callbacks;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Current render's props (set during tryRender, read by nativ_jsi_get_*)
|
|
49
|
+
static thread_local PropsSnapshot* g_currentProps = nullptr;
|
|
50
|
+
static thread_local std::string g_currentComponent;
|
|
51
|
+
|
|
52
|
+
// Current render's JNI state (for view manipulation from user dylibs)
|
|
53
|
+
static thread_local JNIEnv* g_currentEnv = nullptr;
|
|
54
|
+
static thread_local jobject g_currentView = nullptr;
|
|
55
|
+
|
|
56
|
+
// ─── C ABI exports (called by user .so dylibs via constructor) ─────────
|
|
57
|
+
|
|
58
|
+
extern "C" {
|
|
59
|
+
|
|
60
|
+
void nativ_register_sync(const char* moduleId, const char* fnName, NativSyncFn fn) {
|
|
61
|
+
auto key = std::string(moduleId) + "::" + fnName;
|
|
62
|
+
getSyncRegistry()[key] = fn;
|
|
63
|
+
LOGI("registered sync: %s", key.c_str());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void nativ_register_async(const char* moduleId, const char* fnName, NativAsyncFn fn) {
|
|
67
|
+
auto key = std::string(moduleId) + "::" + fnName;
|
|
68
|
+
getAsyncRegistry()[key] = fn;
|
|
69
|
+
LOGI("registered async: %s", key.c_str());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
void nativ_register_render(const char* componentId, NativRenderFn fn) {
|
|
73
|
+
getRenderRegistry()[std::string(componentId)] = fn;
|
|
74
|
+
LOGI("registered render: %s", componentId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Props access (called by Rust/C++ render functions) ────────────────
|
|
78
|
+
|
|
79
|
+
const char* nativ_jsi_get_string(void* runtime, void* object, const char* prop_name) {
|
|
80
|
+
if (!g_currentProps) return "";
|
|
81
|
+
auto it = g_currentProps->strings.find(std::string(prop_name));
|
|
82
|
+
if (it != g_currentProps->strings.end()) {
|
|
83
|
+
static thread_local std::string buf;
|
|
84
|
+
buf = it->second;
|
|
85
|
+
return buf.c_str();
|
|
86
|
+
}
|
|
87
|
+
return "";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
double nativ_jsi_get_number(void* runtime, void* object, const char* prop_name) {
|
|
91
|
+
if (!g_currentProps) return 0.0;
|
|
92
|
+
auto it = g_currentProps->numbers.find(std::string(prop_name));
|
|
93
|
+
if (it != g_currentProps->numbers.end()) return it->second;
|
|
94
|
+
return 0.0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
int nativ_jsi_get_bool(void* runtime, void* object, const char* prop_name) {
|
|
98
|
+
if (!g_currentProps) return 0;
|
|
99
|
+
auto it = g_currentProps->bools.find(std::string(prop_name));
|
|
100
|
+
if (it != g_currentProps->bools.end()) return it->second ? 1 : 0;
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
int nativ_jsi_has_prop(void* runtime, void* object, const char* prop_name) {
|
|
105
|
+
if (!g_currentProps) return 0;
|
|
106
|
+
std::string name(prop_name);
|
|
107
|
+
return (g_currentProps->strings.count(name) ||
|
|
108
|
+
g_currentProps->numbers.count(name) ||
|
|
109
|
+
g_currentProps->bools.count(name)) ? 1 : 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
void nativ_jsi_call_function(void* runtime, void* object, const char* prop_name) {
|
|
113
|
+
// TODO: callbacks via JNI → JS bridge
|
|
114
|
+
LOGI("call_function('%s') — not yet implemented on Android", prop_name);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
void nativ_jsi_call_function_with_string(void* runtime, void* object,
|
|
118
|
+
const char* prop_name, const char* arg) {
|
|
119
|
+
LOGI("call_function_with_string('%s') — not yet implemented on Android", prop_name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Keep view register stubs for ABI compat with iOS dylibs
|
|
123
|
+
void nativ_register_view(const char*, void*) {}
|
|
124
|
+
void nativ_unregister_view(const char*) {}
|
|
125
|
+
|
|
126
|
+
// Direct C dispatch — called from NativBindingsInstaller.cpp
|
|
127
|
+
const char* nativ_call_sync(const char* moduleId, const char* fnName, const char* argsJson) {
|
|
128
|
+
auto key = std::string(moduleId) + "::" + fnName;
|
|
129
|
+
auto ® = getSyncRegistry();
|
|
130
|
+
auto it = reg.find(key);
|
|
131
|
+
if (it == reg.end()) return nullptr;
|
|
132
|
+
return it->second(argsJson);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Async dispatch — returns the function pointer (BindingsInstaller handles Promise + threading)
|
|
136
|
+
NativAsyncFn nativ_get_async_fn(const char* moduleId, const char* fnName) {
|
|
137
|
+
auto key = std::string(moduleId) + "::" + fnName;
|
|
138
|
+
auto ® = getAsyncRegistry();
|
|
139
|
+
auto it = reg.find(key);
|
|
140
|
+
if (it == reg.end()) return nullptr;
|
|
141
|
+
return it->second;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── Android view manipulation (called by Rust/C++ render functions) ──
|
|
145
|
+
|
|
146
|
+
void nativ_view_set_background_color(void* view, double r, double g, double b, double a) {
|
|
147
|
+
if (!g_currentEnv) return;
|
|
148
|
+
JNIEnv* env = g_currentEnv;
|
|
149
|
+
jobject targetView = view ? (jobject)view : g_currentView;
|
|
150
|
+
if (!targetView) return;
|
|
151
|
+
|
|
152
|
+
int color = ((int)(a * 255) << 24) | ((int)(r * 255) << 16) |
|
|
153
|
+
((int)(g * 255) << 8) | (int)(b * 255);
|
|
154
|
+
|
|
155
|
+
jclass viewClass = env->FindClass("android/view/View");
|
|
156
|
+
jmethodID setBg = env->GetMethodID(viewClass, "setBackgroundColor", "(I)V");
|
|
157
|
+
env->CallVoidMethod(targetView, setBg, color);
|
|
158
|
+
env->DeleteLocalRef(viewClass);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
void nativ_view_add_label(void* parent, const char* text,
|
|
162
|
+
double r, double g, double b,
|
|
163
|
+
double width, double height) {
|
|
164
|
+
if (!g_currentEnv) return;
|
|
165
|
+
JNIEnv* env = g_currentEnv;
|
|
166
|
+
jobject parentView = parent ? (jobject)parent : g_currentView;
|
|
167
|
+
if (!parentView) return;
|
|
168
|
+
|
|
169
|
+
// Get context from parent view
|
|
170
|
+
jclass viewClass = env->FindClass("android/view/View");
|
|
171
|
+
jmethodID getContext = env->GetMethodID(viewClass, "getContext",
|
|
172
|
+
"()Landroid/content/Context;");
|
|
173
|
+
jobject context = env->CallObjectMethod(parentView, getContext);
|
|
174
|
+
|
|
175
|
+
// Create TextView
|
|
176
|
+
jclass tvClass = env->FindClass("android/widget/TextView");
|
|
177
|
+
jmethodID tvInit = env->GetMethodID(tvClass, "<init>",
|
|
178
|
+
"(Landroid/content/Context;)V");
|
|
179
|
+
jobject tv = env->NewObject(tvClass, tvInit, context);
|
|
180
|
+
|
|
181
|
+
// setText
|
|
182
|
+
jstring jText = env->NewStringUTF(text);
|
|
183
|
+
jmethodID setText = env->GetMethodID(tvClass, "setText",
|
|
184
|
+
"(Ljava/lang/CharSequence;)V");
|
|
185
|
+
env->CallVoidMethod(tv, setText, jText);
|
|
186
|
+
|
|
187
|
+
// setTextColor
|
|
188
|
+
int color = (0xFF << 24) | ((int)(r * 255) << 16) |
|
|
189
|
+
((int)(g * 255) << 8) | (int)(b * 255);
|
|
190
|
+
jmethodID setColor = env->GetMethodID(tvClass, "setTextColor", "(I)V");
|
|
191
|
+
env->CallVoidMethod(tv, setColor, color);
|
|
192
|
+
|
|
193
|
+
// setGravity(Gravity.CENTER = 17)
|
|
194
|
+
jmethodID setGravity = env->GetMethodID(tvClass, "setGravity", "(I)V");
|
|
195
|
+
env->CallVoidMethod(tv, setGravity, 17);
|
|
196
|
+
|
|
197
|
+
// setTextSize
|
|
198
|
+
jmethodID setTextSize = env->GetMethodID(tvClass, "setTextSize", "(F)V");
|
|
199
|
+
env->CallVoidMethod(tv, setTextSize, 18.0f);
|
|
200
|
+
|
|
201
|
+
// LayoutParams = MATCH_PARENT, MATCH_PARENT
|
|
202
|
+
jclass lpClass = env->FindClass("android/widget/FrameLayout$LayoutParams");
|
|
203
|
+
jmethodID lpInit = env->GetMethodID(lpClass, "<init>", "(II)V");
|
|
204
|
+
jobject lp = env->NewObject(lpClass, lpInit, -1, -1);
|
|
205
|
+
|
|
206
|
+
// addView(tv, lp)
|
|
207
|
+
jclass vgClass = env->FindClass("android/view/ViewGroup");
|
|
208
|
+
jmethodID addView = env->GetMethodID(vgClass, "addView",
|
|
209
|
+
"(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V");
|
|
210
|
+
env->CallVoidMethod(parentView, addView, tv, lp);
|
|
211
|
+
|
|
212
|
+
if (env->ExceptionCheck()) {
|
|
213
|
+
LOGE("add_label: exception after addView");
|
|
214
|
+
env->ExceptionDescribe();
|
|
215
|
+
env->ExceptionClear();
|
|
216
|
+
} else {
|
|
217
|
+
LOGI("add_label: added '%s' to parent", text);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
env->DeleteLocalRef(lp);
|
|
221
|
+
env->DeleteLocalRef(tv);
|
|
222
|
+
env->DeleteLocalRef(jText);
|
|
223
|
+
env->DeleteLocalRef(context);
|
|
224
|
+
env->DeleteLocalRef(lpClass);
|
|
225
|
+
env->DeleteLocalRef(vgClass);
|
|
226
|
+
env->DeleteLocalRef(tvClass);
|
|
227
|
+
env->DeleteLocalRef(viewClass);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
void* nativ_view_add_subview(void* parent, double x, double y,
|
|
231
|
+
double w, double h,
|
|
232
|
+
double r, double g, double b, double a) {
|
|
233
|
+
if (!g_currentEnv) return nullptr;
|
|
234
|
+
JNIEnv* env = g_currentEnv;
|
|
235
|
+
jobject parentView = parent ? (jobject)parent : g_currentView;
|
|
236
|
+
if (!parentView) return nullptr;
|
|
237
|
+
|
|
238
|
+
// Get context
|
|
239
|
+
jclass viewClass = env->FindClass("android/view/View");
|
|
240
|
+
jmethodID getContext = env->GetMethodID(viewClass, "getContext",
|
|
241
|
+
"()Landroid/content/Context;");
|
|
242
|
+
jobject context = env->CallObjectMethod(parentView, getContext);
|
|
243
|
+
|
|
244
|
+
// Create child View
|
|
245
|
+
jobject child = env->NewObject(viewClass,
|
|
246
|
+
env->GetMethodID(viewClass, "<init>", "(Landroid/content/Context;)V"),
|
|
247
|
+
context);
|
|
248
|
+
|
|
249
|
+
// setBackgroundColor
|
|
250
|
+
int color = ((int)(a * 255) << 24) | ((int)(r * 255) << 16) |
|
|
251
|
+
((int)(g * 255) << 8) | (int)(b * 255);
|
|
252
|
+
jmethodID setBg = env->GetMethodID(viewClass, "setBackgroundColor", "(I)V");
|
|
253
|
+
env->CallVoidMethod(child, setBg, color);
|
|
254
|
+
|
|
255
|
+
// Convert dp coords to pixels
|
|
256
|
+
jclass dpClass = env->FindClass("android/util/TypedValue");
|
|
257
|
+
jmethodID applyDim = env->GetStaticMethodID(dpClass, "applyDimension",
|
|
258
|
+
"(IFLandroid/util/DisplayMetrics;)F");
|
|
259
|
+
jclass contextClass = env->FindClass("android/content/Context");
|
|
260
|
+
jmethodID getResources = env->GetMethodID(contextClass, "getResources",
|
|
261
|
+
"()Landroid/content/res/Resources;");
|
|
262
|
+
jobject resources = env->CallObjectMethod(context, getResources);
|
|
263
|
+
jclass resClass = env->GetObjectClass(resources);
|
|
264
|
+
jmethodID getMetrics = env->GetMethodID(resClass, "getDisplayMetrics",
|
|
265
|
+
"()Landroid/util/DisplayMetrics;");
|
|
266
|
+
jobject metrics = env->CallObjectMethod(resources, getMetrics);
|
|
267
|
+
|
|
268
|
+
// TypedValue.COMPLEX_UNIT_DIP = 1
|
|
269
|
+
float px = (float)x; // points ≈ dp on Android
|
|
270
|
+
float py = (float)y;
|
|
271
|
+
float pw = (float)w;
|
|
272
|
+
float ph = (float)h;
|
|
273
|
+
// Scale from points to pixels using density
|
|
274
|
+
jclass metricsClass = env->GetObjectClass(metrics);
|
|
275
|
+
jfieldID densityField = env->GetFieldID(metricsClass, "density", "F");
|
|
276
|
+
float density = env->GetFloatField(metrics, densityField);
|
|
277
|
+
int pxX = (int)(px * density);
|
|
278
|
+
int pxY = (int)(py * density);
|
|
279
|
+
int pxW = (int)(pw * density);
|
|
280
|
+
int pxH = (int)(ph * density);
|
|
281
|
+
|
|
282
|
+
// LayoutParams with position
|
|
283
|
+
jclass lpClass = env->FindClass("android/widget/FrameLayout$LayoutParams");
|
|
284
|
+
jmethodID lpInit = env->GetMethodID(lpClass, "<init>", "(II)V");
|
|
285
|
+
jobject lp = env->NewObject(lpClass, lpInit, pxW, pxH);
|
|
286
|
+
|
|
287
|
+
// Set margins for position (FrameLayout uses margins for positioning)
|
|
288
|
+
jfieldID leftMargin = env->GetFieldID(lpClass, "leftMargin", "I");
|
|
289
|
+
jfieldID topMargin = env->GetFieldID(lpClass, "topMargin", "I");
|
|
290
|
+
env->SetIntField(lp, leftMargin, pxX);
|
|
291
|
+
env->SetIntField(lp, topMargin, pxY);
|
|
292
|
+
|
|
293
|
+
// addView
|
|
294
|
+
jclass vgClass = env->FindClass("android/view/ViewGroup");
|
|
295
|
+
jmethodID addView = env->GetMethodID(vgClass, "addView",
|
|
296
|
+
"(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V");
|
|
297
|
+
env->CallVoidMethod(parentView, addView, child, lp);
|
|
298
|
+
|
|
299
|
+
// Return as global ref (user may reference it later in same render)
|
|
300
|
+
jobject globalChild = env->NewGlobalRef(child);
|
|
301
|
+
|
|
302
|
+
// Clean up local refs
|
|
303
|
+
env->DeleteLocalRef(lp);
|
|
304
|
+
env->DeleteLocalRef(child);
|
|
305
|
+
env->DeleteLocalRef(context);
|
|
306
|
+
env->DeleteLocalRef(resources);
|
|
307
|
+
env->DeleteLocalRef(metrics);
|
|
308
|
+
env->DeleteLocalRef(lpClass);
|
|
309
|
+
env->DeleteLocalRef(vgClass);
|
|
310
|
+
env->DeleteLocalRef(viewClass);
|
|
311
|
+
env->DeleteLocalRef(dpClass);
|
|
312
|
+
env->DeleteLocalRef(contextClass);
|
|
313
|
+
env->DeleteLocalRef(resClass);
|
|
314
|
+
env->DeleteLocalRef(metricsClass);
|
|
315
|
+
|
|
316
|
+
return globalChild;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
} // extern "C"
|
|
320
|
+
|
|
321
|
+
// ─── JNI helpers ───────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
static std::string jstringToString(JNIEnv* env, jstring jstr) {
|
|
324
|
+
if (!jstr) return "";
|
|
325
|
+
const char* chars = env->GetStringUTFChars(jstr, nullptr);
|
|
326
|
+
std::string result(chars);
|
|
327
|
+
env->ReleaseStringUTFChars(jstr, chars);
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Convert a Java Map<String, String> to C++ map
|
|
332
|
+
static std::unordered_map<std::string, std::string> jmapToStringMap(JNIEnv* env, jobject jmap) {
|
|
333
|
+
std::unordered_map<std::string, std::string> result;
|
|
334
|
+
if (!jmap) return result;
|
|
335
|
+
|
|
336
|
+
jclass mapClass = env->GetObjectClass(jmap);
|
|
337
|
+
jmethodID entrySet = env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;");
|
|
338
|
+
jobject set = env->CallObjectMethod(jmap, entrySet);
|
|
339
|
+
|
|
340
|
+
jclass setClass = env->GetObjectClass(set);
|
|
341
|
+
jmethodID iterator = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
|
|
342
|
+
jobject iter = env->CallObjectMethod(set, iterator);
|
|
343
|
+
|
|
344
|
+
jclass iterClass = env->GetObjectClass(iter);
|
|
345
|
+
jmethodID hasNext = env->GetMethodID(iterClass, "hasNext", "()Z");
|
|
346
|
+
jmethodID next = env->GetMethodID(iterClass, "next", "()Ljava/lang/Object;");
|
|
347
|
+
|
|
348
|
+
jclass entryClass = env->FindClass("java/util/Map$Entry");
|
|
349
|
+
jmethodID getKey = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
|
|
350
|
+
jmethodID getValue = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
|
|
351
|
+
|
|
352
|
+
while (env->CallBooleanMethod(iter, hasNext)) {
|
|
353
|
+
jobject entry = env->CallObjectMethod(iter, next);
|
|
354
|
+
auto key = jstringToString(env, (jstring)env->CallObjectMethod(entry, getKey));
|
|
355
|
+
auto val = jstringToString(env, (jstring)env->CallObjectMethod(entry, getValue));
|
|
356
|
+
result[key] = val;
|
|
357
|
+
env->DeleteLocalRef(entry);
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Convert a Java Map<String, Double> to C++ map
|
|
363
|
+
static std::unordered_map<std::string, double> jmapToDoubleMap(JNIEnv* env, jobject jmap) {
|
|
364
|
+
std::unordered_map<std::string, double> result;
|
|
365
|
+
if (!jmap) return result;
|
|
366
|
+
|
|
367
|
+
jclass mapClass = env->GetObjectClass(jmap);
|
|
368
|
+
jmethodID entrySet = env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;");
|
|
369
|
+
jobject set = env->CallObjectMethod(jmap, entrySet);
|
|
370
|
+
|
|
371
|
+
jclass setClass = env->GetObjectClass(set);
|
|
372
|
+
jmethodID iterator = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
|
|
373
|
+
jobject iter = env->CallObjectMethod(set, iterator);
|
|
374
|
+
|
|
375
|
+
jclass iterClass = env->GetObjectClass(iter);
|
|
376
|
+
jmethodID hasNext = env->GetMethodID(iterClass, "hasNext", "()Z");
|
|
377
|
+
jmethodID next = env->GetMethodID(iterClass, "next", "()Ljava/lang/Object;");
|
|
378
|
+
|
|
379
|
+
jclass entryClass = env->FindClass("java/util/Map$Entry");
|
|
380
|
+
jmethodID getKey = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
|
|
381
|
+
jmethodID getValue = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
|
|
382
|
+
|
|
383
|
+
jclass doubleClass = env->FindClass("java/lang/Double");
|
|
384
|
+
jmethodID doubleValue = env->GetMethodID(doubleClass, "doubleValue", "()D");
|
|
385
|
+
|
|
386
|
+
while (env->CallBooleanMethod(iter, hasNext)) {
|
|
387
|
+
jobject entry = env->CallObjectMethod(iter, next);
|
|
388
|
+
auto key = jstringToString(env, (jstring)env->CallObjectMethod(entry, getKey));
|
|
389
|
+
jobject valObj = env->CallObjectMethod(entry, getValue);
|
|
390
|
+
double val = env->CallDoubleMethod(valObj, doubleValue);
|
|
391
|
+
result[key] = val;
|
|
392
|
+
env->DeleteLocalRef(valObj);
|
|
393
|
+
env->DeleteLocalRef(entry);
|
|
394
|
+
}
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Convert a Java Map<String, Boolean> to C++ map
|
|
399
|
+
static std::unordered_map<std::string, bool> jmapToBoolMap(JNIEnv* env, jobject jmap) {
|
|
400
|
+
std::unordered_map<std::string, bool> result;
|
|
401
|
+
if (!jmap) return result;
|
|
402
|
+
|
|
403
|
+
jclass mapClass = env->GetObjectClass(jmap);
|
|
404
|
+
jmethodID entrySet = env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;");
|
|
405
|
+
jobject set = env->CallObjectMethod(jmap, entrySet);
|
|
406
|
+
|
|
407
|
+
jclass setClass = env->GetObjectClass(set);
|
|
408
|
+
jmethodID iterator = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
|
|
409
|
+
jobject iter = env->CallObjectMethod(set, iterator);
|
|
410
|
+
|
|
411
|
+
jclass iterClass = env->GetObjectClass(iter);
|
|
412
|
+
jmethodID hasNext = env->GetMethodID(iterClass, "hasNext", "()Z");
|
|
413
|
+
jmethodID next = env->GetMethodID(iterClass, "next", "()Ljava/lang/Object;");
|
|
414
|
+
|
|
415
|
+
jclass entryClass = env->FindClass("java/util/Map$Entry");
|
|
416
|
+
jmethodID getKey = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
|
|
417
|
+
jmethodID getValue = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
|
|
418
|
+
|
|
419
|
+
jclass boolClass = env->FindClass("java/lang/Boolean");
|
|
420
|
+
jmethodID boolValue = env->GetMethodID(boolClass, "booleanValue", "()Z");
|
|
421
|
+
|
|
422
|
+
while (env->CallBooleanMethod(iter, hasNext)) {
|
|
423
|
+
jobject entry = env->CallObjectMethod(iter, next);
|
|
424
|
+
auto key = jstringToString(env, (jstring)env->CallObjectMethod(entry, getKey));
|
|
425
|
+
jobject valObj = env->CallObjectMethod(entry, getValue);
|
|
426
|
+
bool val = env->CallBooleanMethod(valObj, boolValue);
|
|
427
|
+
result[key] = val;
|
|
428
|
+
env->DeleteLocalRef(valObj);
|
|
429
|
+
env->DeleteLocalRef(entry);
|
|
430
|
+
}
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ─── JNI exports ───────────────────────────────────────────────────────
|
|
435
|
+
|
|
436
|
+
extern "C" {
|
|
437
|
+
|
|
438
|
+
JNIEXPORT void JNICALL
|
|
439
|
+
Java_com_nativfabric_NativRuntime_nativeInit(JNIEnv* env, jobject thiz) {
|
|
440
|
+
LOGI("NativRuntime initialized");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
JNIEXPORT void JNICALL
|
|
444
|
+
Java_com_nativfabric_NativRuntime_nativeTryRender(
|
|
445
|
+
JNIEnv* env, jobject thiz,
|
|
446
|
+
jstring jComponentId, jobject jView, jfloat width, jfloat height,
|
|
447
|
+
jobject jStrings, jobject jNumbers, jobject jBools
|
|
448
|
+
) {
|
|
449
|
+
auto componentId = jstringToString(env, jComponentId);
|
|
450
|
+
LOGI("nativeTryRender: %s (%.0fx%.0f), registry size=%zu", componentId.c_str(), width, height, getRenderRegistry().size());
|
|
451
|
+
auto ® = getRenderRegistry();
|
|
452
|
+
auto it = reg.find(componentId);
|
|
453
|
+
if (it == reg.end()) {
|
|
454
|
+
LOGE("nativeTryRender: render function not found for %s", componentId.c_str());
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
LOGI("nativeTryRender: calling render function for %s", componentId.c_str());
|
|
458
|
+
|
|
459
|
+
// Build props snapshot from Java maps
|
|
460
|
+
PropsSnapshot props;
|
|
461
|
+
props.strings = jmapToStringMap(env, jStrings);
|
|
462
|
+
props.numbers = jmapToDoubleMap(env, jNumbers);
|
|
463
|
+
props.bools = jmapToBoolMap(env, jBools);
|
|
464
|
+
|
|
465
|
+
// Set thread-local state for the render call
|
|
466
|
+
g_currentProps = &props;
|
|
467
|
+
g_currentComponent = componentId;
|
|
468
|
+
g_currentEnv = env;
|
|
469
|
+
g_currentView = jView;
|
|
470
|
+
|
|
471
|
+
// Pass the view jobject as void* — render functions use nativ_view_* APIs
|
|
472
|
+
// which access g_currentEnv + g_currentView internally.
|
|
473
|
+
// Pass non-null sentinels for runtime/props — Android's nativ_jsi_get_*
|
|
474
|
+
// functions read from g_currentProps, but Props::new() requires both non-null.
|
|
475
|
+
static int runtimeSentinel = 1;
|
|
476
|
+
it->second(reinterpret_cast<void*>(jView), width, height,
|
|
477
|
+
reinterpret_cast<void*>(&runtimeSentinel),
|
|
478
|
+
reinterpret_cast<void*>(&props));
|
|
479
|
+
|
|
480
|
+
g_currentProps = nullptr;
|
|
481
|
+
g_currentComponent.clear();
|
|
482
|
+
g_currentEnv = nullptr;
|
|
483
|
+
g_currentView = nullptr;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
JNIEXPORT jstring JNICALL
|
|
487
|
+
Java_com_nativfabric_NativRuntime_nativeCallSync(
|
|
488
|
+
JNIEnv* env, jobject thiz,
|
|
489
|
+
jstring jModuleId, jstring jFnName, jstring jArgsJson
|
|
490
|
+
) {
|
|
491
|
+
auto moduleId = jstringToString(env, jModuleId);
|
|
492
|
+
auto fnName = jstringToString(env, jFnName);
|
|
493
|
+
auto argsJson = jstringToString(env, jArgsJson);
|
|
494
|
+
|
|
495
|
+
auto key = moduleId + "::" + fnName;
|
|
496
|
+
auto ® = getSyncRegistry();
|
|
497
|
+
auto it = reg.find(key);
|
|
498
|
+
if (it == reg.end()) {
|
|
499
|
+
LOGE("Unknown function: %s", key.c_str());
|
|
500
|
+
return nullptr;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const char* result = it->second(argsJson.c_str());
|
|
504
|
+
if (!result) return nullptr;
|
|
505
|
+
return env->NewStringUTF(result);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
} // extern "C"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package com.nativfabric
|
|
2
|
+
|
|
3
|
+
import android.view.ViewGroup
|
|
4
|
+
import android.widget.FrameLayout
|
|
5
|
+
import androidx.compose.runtime.Composable
|
|
6
|
+
import androidx.compose.ui.platform.ComposeView
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ComposeHost — APK-side helper for .dex Compose components.
|
|
10
|
+
*
|
|
11
|
+
* The .dex can't call ComposeView.setContent directly (signature mismatch
|
|
12
|
+
* between compile-time and runtime due to Compose compiler transforms).
|
|
13
|
+
* This helper is compiled WITH the Gradle Compose plugin, so setContent works.
|
|
14
|
+
* The .dex calls ComposeHost.render(parent) { MyComposable(props) } instead.
|
|
15
|
+
*/
|
|
16
|
+
object ComposeHost {
|
|
17
|
+
@JvmStatic
|
|
18
|
+
fun render(parent: ViewGroup, content: @Composable () -> Unit) {
|
|
19
|
+
val composeView = ComposeView(parent.context)
|
|
20
|
+
composeView.setContent(content)
|
|
21
|
+
parent.addView(composeView, FrameLayout.LayoutParams(
|
|
22
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
23
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
24
|
+
))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.nativfabric
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
|
|
10
|
+
class NativContainerPackage : BaseReactPackage() {
|
|
11
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
12
|
+
// Ensure NativRuntime has app context for DexClassLoader
|
|
13
|
+
if (NativRuntime.appContext == null) {
|
|
14
|
+
NativRuntime.appContext = reactContext.applicationContext
|
|
15
|
+
}
|
|
16
|
+
return listOf(NativContainerViewManager())
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
20
|
+
return when (name) {
|
|
21
|
+
NativRuntimeModule.NAME -> NativRuntimeModule(reactContext)
|
|
22
|
+
else -> null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
27
|
+
mapOf(
|
|
28
|
+
NativRuntimeModule.NAME to ReactModuleInfo(
|
|
29
|
+
NativRuntimeModule.NAME,
|
|
30
|
+
NativRuntimeModule.NAME,
|
|
31
|
+
false, false, false, false, true
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package com.nativfabric
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.widget.FrameLayout
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* NativContainerView — hosts native views rendered by Rust/C++/Kotlin/Compose.
|
|
9
|
+
*
|
|
10
|
+
* Simple FrameLayout. Render functions add children directly.
|
|
11
|
+
* We override onLayout to manually measure/layout children since
|
|
12
|
+
* Fabric/Yoga doesn't measure programmatically-added Android views.
|
|
13
|
+
*/
|
|
14
|
+
class NativContainerView(context: Context) : FrameLayout(context) {
|
|
15
|
+
init {
|
|
16
|
+
setBackgroundColor(0x00000000)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
20
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
21
|
+
// Manually measure+layout all children since Fabric doesn't know about them
|
|
22
|
+
val w = right - left
|
|
23
|
+
val h = bottom - top
|
|
24
|
+
if (w <= 0 || h <= 0) return
|
|
25
|
+
val wSpec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY)
|
|
26
|
+
val hSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY)
|
|
27
|
+
for (i in 0 until childCount) {
|
|
28
|
+
val child = getChildAt(i)
|
|
29
|
+
child.measure(wSpec, hSpec)
|
|
30
|
+
child.layout(0, 0, w, h)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun requestLayout() {
|
|
35
|
+
super.requestLayout()
|
|
36
|
+
// Fabric intercepts requestLayout — force a measure pass
|
|
37
|
+
post {
|
|
38
|
+
val w = width
|
|
39
|
+
val h = height
|
|
40
|
+
if (w > 0 && h > 0) {
|
|
41
|
+
val wSpec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY)
|
|
42
|
+
val hSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY)
|
|
43
|
+
for (i in 0 until childCount) {
|
|
44
|
+
val child = getChildAt(i)
|
|
45
|
+
child.measure(wSpec, hSpec)
|
|
46
|
+
child.layout(0, 0, w, h)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package com.nativfabric
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
5
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
7
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
8
|
+
import com.facebook.react.viewmanagers.NativContainerManagerInterface
|
|
9
|
+
import com.facebook.react.viewmanagers.NativContainerManagerDelegate
|
|
10
|
+
|
|
11
|
+
@ReactModule(name = NativContainerViewManager.NAME)
|
|
12
|
+
class NativContainerViewManager : SimpleViewManager<NativContainerView>(),
|
|
13
|
+
NativContainerManagerInterface<NativContainerView> {
|
|
14
|
+
|
|
15
|
+
private val delegate: ViewManagerDelegate<NativContainerView> =
|
|
16
|
+
NativContainerManagerDelegate(this)
|
|
17
|
+
|
|
18
|
+
override fun getDelegate(): ViewManagerDelegate<NativContainerView> = delegate
|
|
19
|
+
|
|
20
|
+
override fun getName(): String = NAME
|
|
21
|
+
|
|
22
|
+
override fun createViewInstance(context: ThemedReactContext): NativContainerView {
|
|
23
|
+
val view = NativContainerView(context)
|
|
24
|
+
view.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
|
25
|
+
val w = right - left
|
|
26
|
+
val h = bottom - top
|
|
27
|
+
val oldW = oldRight - oldLeft
|
|
28
|
+
val oldH = oldBottom - oldTop
|
|
29
|
+
if (w != oldW || h != oldH) {
|
|
30
|
+
renderComponent(view)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return view
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@ReactProp(name = "componentId")
|
|
37
|
+
override fun setComponentId(view: NativContainerView?, value: String?) {
|
|
38
|
+
view?.tag = value
|
|
39
|
+
view?.let { renderComponent(it) }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@ReactProp(name = "propsJson")
|
|
43
|
+
override fun setPropsJson(view: NativContainerView?, value: String?) {
|
|
44
|
+
view?.let { renderComponent(it) }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun renderComponent(view: NativContainerView) {
|
|
48
|
+
val componentId = view.tag as? String ?: return
|
|
49
|
+
if (view.width <= 0 || view.height <= 0) {
|
|
50
|
+
view.post { renderComponent(view) }
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Render directly into the view
|
|
55
|
+
view.removeAllViews()
|
|
56
|
+
NativRuntime.tryRender(componentId, view, view.width.toFloat(), view.height.toFloat())
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
companion object {
|
|
60
|
+
const val NAME = "NativContainer"
|
|
61
|
+
}
|
|
62
|
+
}
|