@reactvision/react-viro 2.53.1 → 2.55.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/README.md +85 -46
- package/android/react_viro/react_viro-release.aar +0 -0
- package/android/viro_renderer/viro_renderer-release.aar +0 -0
- package/components/AR/ViroARCamera.tsx +5 -0
- package/components/AR/ViroARImageMarker.tsx +5 -0
- package/components/AR/ViroARObjectMarker.tsx +5 -0
- package/components/AR/ViroARPlane.tsx +5 -0
- package/components/AR/ViroARPlaneSelector.tsx +5 -0
- package/components/AR/ViroARScene.tsx +5 -0
- package/components/AR/ViroARSceneNavigator.tsx +84 -0
- package/components/AR/ViroCommonProps.ts +11 -0
- package/components/Material/ViroMaterials.ts +51 -0
- package/components/Studio/StudioARScene.tsx +368 -0
- package/components/Studio/StudioSceneNavigator.tsx +191 -0
- package/components/Studio/VRTStudioModule.ts +40 -0
- package/components/Studio/domain/animationRegistry.ts +86 -0
- package/components/Studio/domain/collisionBindingsRuntime.ts +93 -0
- package/components/Studio/domain/collisionPairKey.ts +15 -0
- package/components/Studio/domain/dragConfiguration.ts +48 -0
- package/components/Studio/domain/materialConfig.ts +276 -0
- package/components/Studio/domain/physicsConfig.ts +204 -0
- package/components/Studio/domain/sceneNavigationHandler.ts +150 -0
- package/components/Studio/domain/studioMaterials.ts +33 -0
- package/components/Studio/domain/triggerImageRegistry.ts +64 -0
- package/components/Studio/domain/useStudioShaderTimeUniforms.ts +51 -0
- package/components/Studio/domain/useStudioShaderViewportUniforms.ts +52 -0
- package/components/Studio/domain/viroNodeFactory.tsx +323 -0
- package/components/Studio/index.ts +18 -0
- package/components/Studio/types.ts +164 -0
- package/components/Types/ViroEvents.ts +53 -0
- package/components/Utilities/VRModuleOpenXR.ts +50 -0
- package/components/Utilities/VRQuestNavigatorBridge.ts +168 -0
- package/components/Utilities/ViroPlatform.ts +52 -0
- package/components/Utilities/ViroUtils.tsx +48 -0
- package/components/Utilities/ViroVersion.ts +1 -1
- package/components/Utilities/useAnySourceHover.ts +55 -0
- package/components/Utilities/useAnySourcePressed.ts +70 -0
- package/components/Viro360Image.tsx +7 -0
- package/components/ViroQuestEntryPoint.tsx +79 -0
- package/components/ViroVRSceneNavigator.tsx +44 -19
- package/components/ViroXRSceneNavigator.tsx +217 -0
- package/components/VisionOS/ViroVisionOSModule.ts +93 -0
- package/dist/components/AR/ViroARCamera.d.ts +1 -1
- package/dist/components/AR/ViroARCamera.js +5 -0
- package/dist/components/AR/ViroARImageMarker.d.ts +1 -1
- package/dist/components/AR/ViroARImageMarker.js +5 -0
- package/dist/components/AR/ViroARObjectMarker.d.ts +1 -1
- package/dist/components/AR/ViroARObjectMarker.js +5 -0
- package/dist/components/AR/ViroARPlane.d.ts +1 -1
- package/dist/components/AR/ViroARPlane.js +5 -0
- package/dist/components/AR/ViroARPlaneSelector.d.ts +1 -1
- package/dist/components/AR/ViroARPlaneSelector.js +5 -0
- package/dist/components/AR/ViroARScene.d.ts +1 -1
- package/dist/components/AR/ViroARScene.js +5 -0
- package/dist/components/AR/ViroARSceneNavigator.d.ts +36 -0
- package/dist/components/AR/ViroARSceneNavigator.js +41 -0
- package/dist/components/AR/ViroCommonProps.d.ts +11 -0
- package/dist/components/Material/ViroMaterials.d.ts +12 -0
- package/dist/components/Material/ViroMaterials.js +25 -0
- package/dist/components/ReactVisionClient.d.ts +25 -0
- package/dist/components/ReactVisionClient.js +11 -0
- package/dist/components/Studio/StudioARScene.d.ts +15 -0
- package/dist/components/Studio/StudioARScene.js +299 -0
- package/dist/components/Studio/StudioSceneNavigator.d.ts +31 -0
- package/dist/components/Studio/StudioSceneNavigator.js +174 -0
- package/dist/components/Studio/VRTStudioModule.d.ts +15 -0
- package/dist/components/Studio/VRTStudioModule.js +31 -0
- package/dist/components/Studio/domain/animationRegistry.d.ts +11 -0
- package/dist/components/Studio/domain/animationRegistry.js +67 -0
- package/dist/components/Studio/domain/collisionBindingsRuntime.d.ts +21 -0
- package/dist/components/Studio/domain/collisionBindingsRuntime.js +54 -0
- package/dist/components/Studio/domain/collisionPairKey.d.ts +8 -0
- package/dist/components/Studio/domain/collisionPairKey.js +15 -0
- package/dist/components/Studio/domain/dragConfiguration.d.ts +20 -0
- package/dist/components/Studio/domain/dragConfiguration.js +37 -0
- package/dist/components/Studio/domain/materialConfig.d.ts +56 -0
- package/dist/components/Studio/domain/materialConfig.js +239 -0
- package/dist/components/Studio/domain/physicsConfig.d.ts +69 -0
- package/dist/components/Studio/domain/physicsConfig.js +165 -0
- package/dist/components/Studio/domain/sceneNavigationHandler.d.ts +12 -0
- package/dist/components/Studio/domain/sceneNavigationHandler.js +112 -0
- package/dist/components/Studio/domain/studioMaterials.d.ts +6 -0
- package/dist/components/Studio/domain/studioMaterials.js +30 -0
- package/dist/components/Studio/domain/triggerImageRegistry.d.ts +13 -0
- package/dist/components/Studio/domain/triggerImageRegistry.js +47 -0
- package/dist/components/Studio/domain/useStudioShaderTimeUniforms.d.ts +6 -0
- package/dist/components/Studio/domain/useStudioShaderTimeUniforms.js +48 -0
- package/dist/components/Studio/domain/useStudioShaderViewportUniforms.d.ts +6 -0
- package/dist/components/Studio/domain/useStudioShaderViewportUniforms.js +48 -0
- package/dist/components/Studio/domain/viroNodeFactory.d.ts +28 -0
- package/dist/components/Studio/domain/viroNodeFactory.js +193 -0
- package/dist/components/Studio/index.d.ts +3 -0
- package/dist/components/Studio/index.js +7 -0
- package/dist/components/Studio/types.d.ts +149 -0
- package/dist/components/Studio/types.js +4 -0
- package/dist/components/Types/ViroEvents.d.ts +49 -1
- package/dist/components/Types/ViroEvents.js +1 -0
- package/dist/components/Utilities/VRModuleOpenXR.d.ts +32 -0
- package/dist/components/Utilities/VRModuleOpenXR.js +44 -0
- package/dist/components/Utilities/VRQuestNavigatorBridge.d.ts +85 -0
- package/dist/components/Utilities/VRQuestNavigatorBridge.js +124 -0
- package/dist/components/Utilities/ViroPlatform.d.ts +10 -0
- package/dist/components/Utilities/ViroPlatform.js +43 -0
- package/dist/components/Utilities/ViroUtils.d.ts +19 -0
- package/dist/components/Utilities/ViroUtils.js +34 -0
- package/dist/components/Utilities/ViroVersion.d.ts +1 -1
- package/dist/components/Utilities/ViroVersion.js +1 -1
- package/dist/components/Utilities/useAnySourceHover.d.ts +36 -0
- package/dist/components/Utilities/useAnySourceHover.js +48 -0
- package/dist/components/Utilities/useAnySourcePressed.d.ts +37 -0
- package/dist/components/Utilities/useAnySourcePressed.js +61 -0
- package/dist/components/Viro360Image.d.ts +7 -0
- package/dist/components/ViroQuestEntryPoint.d.ts +13 -0
- package/dist/components/ViroQuestEntryPoint.js +104 -0
- package/dist/components/ViroVRSceneNavigator.d.ts +24 -10
- package/dist/components/ViroVRSceneNavigator.js +21 -18
- package/dist/components/ViroXRSceneNavigator.d.ts +54 -0
- package/dist/components/ViroXRSceneNavigator.js +173 -0
- package/dist/components/VisionOS/ViroVisionOSModule.d.ts +65 -0
- package/dist/components/VisionOS/ViroVisionOSModule.js +91 -0
- package/dist/index.d.ts +16 -3
- package/dist/index.js +34 -2
- package/dist/plugins/withViro.d.ts +28 -1
- package/dist/plugins/withViroAndroid.js +312 -7
- package/dist/plugins/withViroIos.js +17 -8
- package/dist/plugins/withViroVisionOS.d.ts +24 -0
- package/dist/plugins/withViroVisionOS.js +265 -0
- package/index.ts +66 -0
- package/ios/ViroReact.podspec +15 -5
- package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreCoreMLSemanticsResources.bundle/Info.plist +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreResources.bundle/Info.plist +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSession.h +30 -1
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSessioniOS.h +16 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROGLTFLoader.h +34 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputControllerBase.h +74 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputType.h +11 -3
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROMaterial.h +29 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROMorpher.h +4 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPlatformUtil.h +13 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPortal.h +17 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderContext.h +41 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderer.h +23 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROSemantics.h +14 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROViewAR.h +11 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Shaders.dat +1 -1
- package/ios/dist/ViroRenderer/ViroKit.framework/ViroKit +0 -0
- package/ios/dist/ViroRenderer/ViroKit.podspec +5 -0
- package/ios/dist/include/VRT360Image.h +1 -0
- package/ios/dist/include/VRTARSceneNavigator.h +7 -0
- package/ios/dist/include/VRTStudioModule.h +6 -0
- package/ios/dist/lib/libViroReact.a +0 -0
- package/package.json +8 -8
|
@@ -46,9 +46,9 @@ const withBranchAndroid = (config) => {
|
|
|
46
46
|
const viroPlugin = config?.plugins?.find((plugin) => Array.isArray(plugin) && plugin[0] === "@reactvision/react-viro");
|
|
47
47
|
if (Array.isArray(viroPlugin)) {
|
|
48
48
|
if (Array.isArray(viroPlugin[1].android?.xRMode)) {
|
|
49
|
-
viroPluginConfig = (viroPlugin[1].android?.xRMode).filter((mode) => ["AR", "GVR", "OVR_MOBILE"].includes(mode));
|
|
49
|
+
viroPluginConfig = (viroPlugin[1].android?.xRMode).filter((mode) => ["AR", "GVR", "OVR_MOBILE", "QUEST"].includes(mode));
|
|
50
50
|
}
|
|
51
|
-
else if (["AR", "GVR", "OVR_MOBILE"].includes(viroPlugin[1]?.android?.xRMode)) {
|
|
51
|
+
else if (["AR", "GVR", "OVR_MOBILE", "QUEST"].includes(viroPlugin[1]?.android?.xRMode)) {
|
|
52
52
|
viroPluginConfig = [viroPlugin[1]?.android.xRMode];
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -197,6 +197,14 @@ const withViroManifest = (config) => (0, config_plugins_1.withAndroidManifest)(c
|
|
|
197
197
|
},
|
|
198
198
|
});
|
|
199
199
|
}
|
|
200
|
+
if (pluginOptions.rvEndpoint) {
|
|
201
|
+
contents?.manifest?.application?.[0]["meta-data"]?.push({
|
|
202
|
+
$: {
|
|
203
|
+
"android:name": "com.reactvision.RVEndpoint",
|
|
204
|
+
"android:value": pluginOptions.rvEndpoint,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
200
208
|
// Add location permissions when geospatial provider is active
|
|
201
209
|
if (geospatialAnchorProvider === "arcore" || geospatialAnchorProvider === "reactvision") {
|
|
202
210
|
const existingPermissions = (contents.manifest["uses-permission"] || [])
|
|
@@ -281,14 +289,311 @@ const withViroManifest = (config) => (0, config_plugins_1.withAndroidManifest)(c
|
|
|
281
289
|
"tools:replace": "required",
|
|
282
290
|
},
|
|
283
291
|
});
|
|
292
|
+
// Quest-specific features and permissions — after uses-feature is initialized
|
|
293
|
+
if (viroPluginConfig.includes("QUEST")) {
|
|
294
|
+
contents.manifest["uses-feature"].push({
|
|
295
|
+
$: {
|
|
296
|
+
"android:name": "android.hardware.vr.headtracking",
|
|
297
|
+
"android:required": "true",
|
|
298
|
+
"android:version": "1",
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
contents.manifest["uses-feature"].push({
|
|
302
|
+
$: {
|
|
303
|
+
"android:name": "oculus.software.handtracking",
|
|
304
|
+
"android:required": "false",
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
// XR_FB_passthrough requires this uses-feature; without it the OpenXR
|
|
308
|
+
// runtime silently strips the extension. required=false so apps that
|
|
309
|
+
// only render fully-virtual scenes still install on Quest 2 etc.
|
|
310
|
+
contents.manifest["uses-feature"].push({
|
|
311
|
+
$: {
|
|
312
|
+
"android:name": "com.oculus.feature.PASSTHROUGH",
|
|
313
|
+
"android:required": "false",
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
const existingPermissions = (contents.manifest["uses-permission"] || [])
|
|
317
|
+
.map((p) => p.$?.["android:name"]);
|
|
318
|
+
if (!existingPermissions.includes("com.oculus.permission.HAND_TRACKING")) {
|
|
319
|
+
contents.manifest["uses-permission"].push({
|
|
320
|
+
$: { "android:name": "com.oculus.permission.HAND_TRACKING" },
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (!existingPermissions.includes("com.oculus.permission.EYE_TRACKING")) {
|
|
324
|
+
contents.manifest["uses-permission"].push({
|
|
325
|
+
$: { "android:name": "com.oculus.permission.EYE_TRACKING" },
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
284
329
|
return newConfig;
|
|
285
330
|
});
|
|
331
|
+
// ── Quest VRActivity generation ────────────────────────────────────────────────
|
|
332
|
+
/**
|
|
333
|
+
* When QUEST mode is active, generate VRActivity.kt in the app's android source
|
|
334
|
+
* and register it in AndroidManifest with com.oculus.intent.category.VR.
|
|
335
|
+
*
|
|
336
|
+
* VRActivity is a ReactActivity that mounts "VRQuestScene" — a root component
|
|
337
|
+
* the app must register via AppRegistry. Running in a separate Activity with the
|
|
338
|
+
* VR intent category causes Horizon OS to grant exclusive OpenXR display access.
|
|
339
|
+
*/
|
|
340
|
+
const withViroQuestActivity = (config, props) => {
|
|
341
|
+
// Read xRMode directly from config.plugins. We cannot rely on the
|
|
342
|
+
// module-level `viroPluginConfig` here: that variable is only updated
|
|
343
|
+
// when `withBranchAndroid`'s withDangerousMod callback runs (mod-apply
|
|
344
|
+
// time), which is *after* this chain-time check executes. On the first
|
|
345
|
+
// prebuild of a new project, viroPluginConfig still holds the default
|
|
346
|
+
// ["AR", "GVR"] when this plugin is composed, and QUEST mods would be
|
|
347
|
+
// silently skipped — no VRActivity.kt and no manifest entry.
|
|
348
|
+
const viroPluginEntry = config?.plugins?.find((plugin) => Array.isArray(plugin) && plugin[0] === "@reactvision/react-viro");
|
|
349
|
+
let xrModes = ["AR", "GVR"];
|
|
350
|
+
if (Array.isArray(viroPluginEntry)) {
|
|
351
|
+
const xrMode = viroPluginEntry[1]?.android?.xRMode;
|
|
352
|
+
if (Array.isArray(xrMode)) {
|
|
353
|
+
xrModes = xrMode.filter((m) => ["AR", "GVR", "OVR_MOBILE", "QUEST"].includes(m));
|
|
354
|
+
}
|
|
355
|
+
else if (typeof xrMode === "string" &&
|
|
356
|
+
["AR", "GVR", "OVR_MOBILE", "QUEST"].includes(xrMode)) {
|
|
357
|
+
xrModes = [xrMode];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (!xrModes.includes("QUEST"))
|
|
361
|
+
return config;
|
|
362
|
+
// 1. Generate VRActivity.kt
|
|
363
|
+
config = (0, config_plugins_1.withDangerousMod)(config, [
|
|
364
|
+
"android",
|
|
365
|
+
async (config) => {
|
|
366
|
+
const packageName = config?.android?.package ?? "";
|
|
367
|
+
const packagePath = packageName.split(".");
|
|
368
|
+
const activityDir = path_1.default.join(config.modRequest.platformProjectRoot, "app", "src", "main", "java", ...packagePath);
|
|
369
|
+
if (!fs_1.default.existsSync(activityDir)) {
|
|
370
|
+
fs_1.default.mkdirSync(activityDir, { recursive: true });
|
|
371
|
+
}
|
|
372
|
+
const activityPath = path_1.default.join(activityDir, "VRActivity.kt");
|
|
373
|
+
// Only write if not already present (preserve manual edits)
|
|
374
|
+
if (!fs_1.default.existsSync(activityPath)) {
|
|
375
|
+
const kotlinContent = `package ${packageName}
|
|
376
|
+
|
|
377
|
+
import android.app.Activity
|
|
378
|
+
import android.app.Application
|
|
379
|
+
import android.os.Bundle
|
|
380
|
+
import android.os.Handler
|
|
381
|
+
import android.os.Looper
|
|
382
|
+
import com.facebook.react.ReactActivity
|
|
383
|
+
import com.facebook.react.ReactActivityDelegate
|
|
384
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
|
385
|
+
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
|
386
|
+
import com.facebook.react.runtime.ReactHostImpl
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* VRActivity — generated by @reactvision/react-viro Expo plugin.
|
|
390
|
+
* Carries com.oculus.intent.category.VR so Horizon OS grants exclusive
|
|
391
|
+
* OpenXR display access. Mounts the "VRQuestScene" React root component.
|
|
392
|
+
* Launch via NativeModules.VRLauncher.launchVRScene().
|
|
393
|
+
*
|
|
394
|
+
* Lifecycle (requires React Native >= 0.83 / Expo >= 55):
|
|
395
|
+
* VRActivity and MainActivity share one ReactHostImpl singleton. On Quest,
|
|
396
|
+
* VRActivity launches into a separate task (FLAG_ACTIVITY_NEW_TASK), so
|
|
397
|
+
* MainActivity.onPause is dispatched *later* via ActivityManager IPC —
|
|
398
|
+
* often after VRActivity.onResume has already promoted the host to RESUMED
|
|
399
|
+
* with currentActivity = VRActivity. When that delayed onPause arrives,
|
|
400
|
+
* the standard delegate calls onHostPause(MainActivity); the identity-
|
|
401
|
+
* mismatch assertion is downgraded to a warning by
|
|
402
|
+
* skipActivityIdentityAssertionOnHostPause (set in VRLauncherModule before
|
|
403
|
+
* startActivity), but the rest of onHostPause runs unconditionally:
|
|
404
|
+
* devSupport is disabled and JavaTimerManager clears its TIMERS_EVENTS
|
|
405
|
+
* callback. Result: timers / requestAnimationFrame stop, Metro Fast Refresh
|
|
406
|
+
* dies.
|
|
407
|
+
*
|
|
408
|
+
* We catch this by listening for that delayed MainActivity.onPause via
|
|
409
|
+
* Application.ActivityLifecycleCallbacks.onActivityPaused(other) and re-
|
|
410
|
+
* promoting the host on the main looper. A Handler.post inside our own
|
|
411
|
+
* onResume would fire too early (the ActivityManager hasn't queued
|
|
412
|
+
* MainActivity.onPause yet), which is why we hook the actual pause event.
|
|
413
|
+
*
|
|
414
|
+
* On exit, VRActivity.onPause runs while currentActivity == this, so the
|
|
415
|
+
* identity assertion passes; MainActivity.onResume then re-promotes via
|
|
416
|
+
* its standard delegate. We do *not* re-promote on a self-pause — the
|
|
417
|
+
* onActivityPaused callback skips when other === this.
|
|
418
|
+
*
|
|
419
|
+
* Mutual exclusion with MainActivity:
|
|
420
|
+
* Quest will happily run MainActivity (panel) and VRActivity (immersive) at
|
|
421
|
+
* the same time if the user taps the app icon in the dock while VR is
|
|
422
|
+
* running. The same Application.ActivityLifecycleCallbacks finishes self
|
|
423
|
+
* when any other Activity in this app resumes — returning the user cleanly
|
|
424
|
+
* to the panel.
|
|
425
|
+
*
|
|
426
|
+
* Surface cleanup on destroy:
|
|
427
|
+
* When VRActivity is destroyed we unload its React surface (reactSurface.stop()
|
|
428
|
+
* in bridgeless arch) so its component tree — including all VRT* components
|
|
429
|
+
* — is unmounted. Without this, the React surface stayed alive in the shared
|
|
430
|
+
* ReactHost across VR re-entries, and on the second/third VR launch each
|
|
431
|
+
* stale surface remounted alongside the fresh one. The C++ scene-graph
|
|
432
|
+
* resolves a click hit to a single viewTag, but multiple VRTComponents
|
|
433
|
+
* claimed it across surfaces — most with a ReactContext bound to a
|
|
434
|
+
* destroyed VRActivity. Click events to the stale ReactContexts crashed in
|
|
435
|
+
* ComponentEventDelegate as IllegalArgumentException SoftExceptions and were
|
|
436
|
+
* silently dropped, so onClick took dozens of presses to register.
|
|
437
|
+
* We still no-op the *delegate* onDestroy() so MainActivity keeps its
|
|
438
|
+
* ReactHost; we only stop the surface explicitly.
|
|
439
|
+
*/
|
|
440
|
+
class VRActivity : ReactActivity() {
|
|
441
|
+
|
|
442
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
443
|
+
private var lifecycleCallbacks: Application.ActivityLifecycleCallbacks? = null
|
|
444
|
+
|
|
445
|
+
// Set true on every VRActivity.onResume; consumed by the first
|
|
446
|
+
// onActivityPaused(other) that follows. One-shot — defends against the
|
|
447
|
+
// single MainActivity.onPause that the NEW_TASK ordering schedules late.
|
|
448
|
+
// Without one-shot semantics, every subsequent foreign pause would re-fire
|
|
449
|
+
// ReactHostImpl.onHostResume, which re-fires every LifecycleEventListener
|
|
450
|
+
// (AppState → "active" → ViroXRSceneNavigator's AppState handler calls
|
|
451
|
+
// launchVRScene() again → visible scene flicker).
|
|
452
|
+
private var expectingForeignPause = false
|
|
453
|
+
|
|
454
|
+
override fun getMainComponentName(): String = "VRQuestScene"
|
|
455
|
+
|
|
456
|
+
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
|
457
|
+
object : DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) {
|
|
458
|
+
// VRActivity destruction MUST NOT tear down the shared ReactHost.
|
|
459
|
+
// MainActivity is still alive (and re-foreground after VR exit) and
|
|
460
|
+
// depends on the same ReactContext. The standard delegate's
|
|
461
|
+
// onDestroy forwards to ReactHostImpl.onHostDestroy(VR), which
|
|
462
|
+
// fires LifecycleEventListener.onHostDestroy on every native module
|
|
463
|
+
// — releasing native resources MainActivity still uses. The next
|
|
464
|
+
// Cmd+R reload then runs the JS bundle re-init against a partially
|
|
465
|
+
// destroyed native side and fails with
|
|
466
|
+
// [runtime not ready]: TypeError: Cannot read property
|
|
467
|
+
// 'EventEmitter' of undefined
|
|
468
|
+
// followed by AppRegistryBinding::stopSurface failed and the
|
|
469
|
+
// surface never recovers. By no-op'ing onDestroy here, host
|
|
470
|
+
// destruction is left to MainActivity's standard delegate (the real
|
|
471
|
+
// last activity). The surface itself is stopped explicitly in
|
|
472
|
+
// VRActivity.onDestroy() below.
|
|
473
|
+
override fun onDestroy() {
|
|
474
|
+
// intentionally no-op — see comment above
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
override fun onResume() {
|
|
479
|
+
super.onResume() // delegate.onResume() -> reactHost.onHostResume(this)
|
|
480
|
+
expectingForeignPause = true
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// onPause not overridden: the default delegate forwards to
|
|
484
|
+
// reactHost.onHostPause(this) while currentActivity matches us, which is
|
|
485
|
+
// what we want.
|
|
486
|
+
|
|
487
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
488
|
+
super.onCreate(savedInstanceState)
|
|
489
|
+
val callbacks = object : Application.ActivityLifecycleCallbacks {
|
|
490
|
+
override fun onActivityResumed(other: Activity) {
|
|
491
|
+
if (other !== this@VRActivity && !isFinishing && !isDestroyed) {
|
|
492
|
+
finish()
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
override fun onActivityPaused(other: Activity) {
|
|
496
|
+
if (other === this@VRActivity || isFinishing || isDestroyed) return
|
|
497
|
+
if (!expectingForeignPause) return
|
|
498
|
+
expectingForeignPause = false
|
|
499
|
+
// MainActivity.onPause was dispatched after our onResume (the
|
|
500
|
+
// NEW_TASK race). The standard delegate already called
|
|
501
|
+
// onHostPause(MainActivity), demoting the host to PAUSED and
|
|
502
|
+
// disabling devSupport / clearing JavaTimerManager's frame
|
|
503
|
+
// callback. Re-promote so VR keeps timers + Fast Refresh.
|
|
504
|
+
mainHandler.post {
|
|
505
|
+
if (isFinishing || isDestroyed) return@post
|
|
506
|
+
(reactHost as? ReactHostImpl)?.onHostResume(this@VRActivity)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
override fun onActivityCreated(a: Activity, b: Bundle?) {}
|
|
510
|
+
override fun onActivityStarted(a: Activity) {}
|
|
511
|
+
override fun onActivityStopped(a: Activity) {}
|
|
512
|
+
override fun onActivitySaveInstanceState(a: Activity, b: Bundle) {}
|
|
513
|
+
override fun onActivityDestroyed(a: Activity) {}
|
|
514
|
+
}
|
|
515
|
+
application.registerActivityLifecycleCallbacks(callbacks)
|
|
516
|
+
lifecycleCallbacks = callbacks
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
override fun onDestroy() {
|
|
520
|
+
// Stop the React surface attached to this VRActivity so its component
|
|
521
|
+
// tree unmounts cleanly. In bridgeless arch this calls
|
|
522
|
+
// reactSurface.stop() + reactSurface = null, leaving the shared
|
|
523
|
+
// ReactHost intact for MainActivity. Without this the React tree from
|
|
524
|
+
// each VR session piles up across re-entries and stale VRTComponents
|
|
525
|
+
// intercept hit-tested viewTags with dead ReactContexts, breaking
|
|
526
|
+
// onClick / onHover until you press dozens of times.
|
|
527
|
+
try {
|
|
528
|
+
reactDelegate?.unloadApp()
|
|
529
|
+
} catch (t: Throwable) {
|
|
530
|
+
android.util.Log.w("VRActivity", "reactDelegate.unloadApp() failed", t)
|
|
531
|
+
}
|
|
532
|
+
lifecycleCallbacks?.let { application.unregisterActivityLifecycleCallbacks(it) }
|
|
533
|
+
lifecycleCallbacks = null
|
|
534
|
+
mainHandler.removeCallbacksAndMessages(null)
|
|
535
|
+
super.onDestroy()
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
`;
|
|
539
|
+
fs_1.default.writeFileSync(activityPath, kotlinContent, "utf-8");
|
|
540
|
+
}
|
|
541
|
+
return config;
|
|
542
|
+
},
|
|
543
|
+
]);
|
|
544
|
+
// 2. Add VRActivity to AndroidManifest
|
|
545
|
+
config = (0, config_plugins_1.withAndroidManifest)(config, async (config) => {
|
|
546
|
+
const app = config.modResults.manifest.application?.[0];
|
|
547
|
+
if (!app)
|
|
548
|
+
return config;
|
|
549
|
+
if (!app.activity)
|
|
550
|
+
app.activity = [];
|
|
551
|
+
const alreadyAdded = app.activity.some((a) => a.$?.["android:name"] === ".VRActivity");
|
|
552
|
+
if (!alreadyAdded) {
|
|
553
|
+
app.activity.push({
|
|
554
|
+
$: {
|
|
555
|
+
"android:name": ".VRActivity",
|
|
556
|
+
"android:screenOrientation": "landscape",
|
|
557
|
+
"android:exported": "false",
|
|
558
|
+
"android:configChanges": "keyboard|keyboardHidden|orientation|screenSize|uiMode",
|
|
559
|
+
"android:launchMode": "singleTask",
|
|
560
|
+
},
|
|
561
|
+
"intent-filter": [
|
|
562
|
+
{
|
|
563
|
+
action: [{ $: { "android:name": "android.intent.action.MAIN" } }],
|
|
564
|
+
category: [
|
|
565
|
+
{ $: { "android:name": "com.oculus.intent.category.VR" } },
|
|
566
|
+
],
|
|
567
|
+
},
|
|
568
|
+
],
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
// Inject com.oculus.app_id into <application> for Meta Quest App Name
|
|
572
|
+
const questAppId = props?.android?.questAppId;
|
|
573
|
+
if (questAppId) {
|
|
574
|
+
if (!app["meta-data"])
|
|
575
|
+
app["meta-data"] = [];
|
|
576
|
+
const alreadyHasAppId = app["meta-data"].some((m) => m.$?.["android:name"] === "com.oculus.app_id");
|
|
577
|
+
if (!alreadyHasAppId) {
|
|
578
|
+
app["meta-data"].push({
|
|
579
|
+
$: {
|
|
580
|
+
"android:name": "com.oculus.app_id",
|
|
581
|
+
"android:value": questAppId,
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return config;
|
|
587
|
+
});
|
|
588
|
+
return config;
|
|
589
|
+
};
|
|
286
590
|
const withViroAndroid = (config, props) => {
|
|
287
|
-
|
|
288
|
-
withViroProjectBuildGradle(config);
|
|
289
|
-
withViroManifest(config);
|
|
290
|
-
withViroSettingsGradle(config);
|
|
291
|
-
withViroAppBuildGradle(config);
|
|
591
|
+
config = withBranchAndroid(config, props);
|
|
592
|
+
config = withViroProjectBuildGradle(config);
|
|
593
|
+
config = withViroManifest(config);
|
|
594
|
+
config = withViroSettingsGradle(config);
|
|
595
|
+
config = withViroAppBuildGradle(config);
|
|
596
|
+
config = withViroQuestActivity(config, props);
|
|
292
597
|
return config;
|
|
293
598
|
};
|
|
294
599
|
exports.withViroAndroid = withViroAndroid;
|
|
@@ -18,6 +18,7 @@ const withViroPods = (config) => {
|
|
|
18
18
|
let geospatialAnchorProvider;
|
|
19
19
|
let iosLinkage;
|
|
20
20
|
let includeARCore;
|
|
21
|
+
let includeSemantics;
|
|
21
22
|
if (Array.isArray(config.plugins)) {
|
|
22
23
|
const pluginConfig = config?.plugins?.find((plugin) => Array.isArray(plugin) && plugin[0] === "@reactvision/react-viro");
|
|
23
24
|
if (Array.isArray(pluginConfig) && pluginConfig.length > 1) {
|
|
@@ -30,6 +31,7 @@ const withViroPods = (config) => {
|
|
|
30
31
|
geospatialAnchorProvider = legacyOpts.geospatialAnchorProvider ?? options.provider ?? defaultProvider;
|
|
31
32
|
iosLinkage = options.iosLinkage;
|
|
32
33
|
includeARCore = options.ios?.includeARCore;
|
|
34
|
+
includeSemantics = options.ios?.includeSemantics;
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
fs_1.default.readFile(`${root}/Podfile`, "utf-8", (err, data) => {
|
|
@@ -44,36 +46,38 @@ const withViroPods = (config) => {
|
|
|
44
46
|
` # Automatically includes Fabric components when RCT_NEW_ARCH_ENABLED=1\n` +
|
|
45
47
|
` pod 'ViroReact', :path => '../node_modules/@reactvision/react-viro/ios'\n` +
|
|
46
48
|
` pod 'ViroKit', :path => '../node_modules/@reactvision/react-viro/ios/dist/ViroRenderer/'`;
|
|
47
|
-
// Add ARCore pods if enabled (explicitly via includeARCore or implicitly via
|
|
49
|
+
// Add ARCore pods if enabled (explicitly via includeARCore/includeSemantics or implicitly via providers)
|
|
48
50
|
// ViroKit.podspec declares these as weak_frameworks, making ARCore optional at runtime
|
|
49
51
|
const needsARCoreForFeatures = cloudAnchorProvider === "arcore" || geospatialAnchorProvider === "arcore";
|
|
50
52
|
const shouldIncludeARCore = includeARCore === true || needsARCoreForFeatures;
|
|
51
|
-
|
|
53
|
+
const shouldIncludeSemantics = shouldIncludeARCore || includeSemantics === true;
|
|
54
|
+
if (shouldIncludeSemantics) {
|
|
52
55
|
viroPods +=
|
|
53
56
|
`\n\n # ARCore SDK - Cloud Anchors, Geospatial, and Scene Semantics API\n` +
|
|
54
57
|
` # ViroKit uses weak linking for these frameworks, making ARCore optional at runtime.\n` +
|
|
55
58
|
` # ViroKit checks availability using NSClassFromString and gracefully degrades if not present.\n` +
|
|
56
59
|
` pod 'ARCore/CloudAnchors', '~> 1.51.0'`;
|
|
57
|
-
// Add Geospatial pod if geospatial is enabled or
|
|
60
|
+
// Add Geospatial pod if geospatial is enabled or full ARCore inclusion
|
|
58
61
|
if (geospatialAnchorProvider === "arcore" || includeARCore === true) {
|
|
59
62
|
viroPods +=
|
|
60
63
|
`\n pod 'ARCore/Geospatial', '~> 1.51.0'`;
|
|
61
64
|
}
|
|
62
65
|
// Add Semantics pod for Scene Semantics API (ML-based scene understanding)
|
|
66
|
+
// Included whenever ARCore is present OR includeSemantics: true
|
|
63
67
|
viroPods +=
|
|
64
68
|
`\n pod 'ARCore/Semantics', '~> 1.51.0'`;
|
|
65
69
|
}
|
|
66
70
|
// Add use_frameworks! if configured
|
|
67
|
-
// User's iosLinkage setting is respected; if not set and ARCore is enabled, default to dynamic
|
|
68
|
-
const effectiveLinkage = iosLinkage || (
|
|
71
|
+
// User's iosLinkage setting is respected; if not set and ARCore/Semantics is enabled, default to dynamic
|
|
72
|
+
const effectiveLinkage = iosLinkage || (shouldIncludeSemantics ? "dynamic" : undefined);
|
|
69
73
|
if (effectiveLinkage) {
|
|
70
74
|
// Insert use_frameworks! before the target block
|
|
71
75
|
let linkageComment;
|
|
72
|
-
if (
|
|
76
|
+
if (shouldIncludeSemantics && effectiveLinkage === "static") {
|
|
73
77
|
// Warn user that static linkage may not work with ARCore
|
|
74
78
|
linkageComment = `# WARNING: ARCore SDK typically requires dynamic frameworks.\n# Static linkage is set but may cause build issues with ARCore pods.`;
|
|
75
79
|
}
|
|
76
|
-
else if (
|
|
80
|
+
else if (shouldIncludeSemantics) {
|
|
77
81
|
linkageComment = `# Framework linkage: ${effectiveLinkage} (ARCore requires dynamic frameworks)`;
|
|
78
82
|
}
|
|
79
83
|
else {
|
|
@@ -167,6 +171,7 @@ const withDefaultInfoPlist = (config, _props) => {
|
|
|
167
171
|
let googleCloudApiKey;
|
|
168
172
|
let rvApiKey;
|
|
169
173
|
let rvProjectId;
|
|
174
|
+
let rvEndpoint;
|
|
170
175
|
let cloudAnchorProvider;
|
|
171
176
|
let geospatialAnchorProvider;
|
|
172
177
|
let includeARCore;
|
|
@@ -186,6 +191,7 @@ const withDefaultInfoPlist = (config, _props) => {
|
|
|
186
191
|
googleCloudApiKey = pluginOptions.googleCloudApiKey;
|
|
187
192
|
rvApiKey = pluginOptions.rvApiKey;
|
|
188
193
|
rvProjectId = pluginOptions.rvProjectId;
|
|
194
|
+
rvEndpoint = pluginOptions.rvEndpoint;
|
|
189
195
|
// Resolve unified provider prop; old props override for backward compat.
|
|
190
196
|
// Default to "reactvision" only when rvApiKey is present (implies RV intent).
|
|
191
197
|
const defaultProvider2 = pluginOptions.rvApiKey ? "reactvision" : undefined;
|
|
@@ -220,6 +226,9 @@ const withDefaultInfoPlist = (config, _props) => {
|
|
|
220
226
|
if (rvProjectId) {
|
|
221
227
|
config.ios.infoPlist.RVProjectId = rvProjectId;
|
|
222
228
|
}
|
|
229
|
+
if (rvEndpoint) {
|
|
230
|
+
config.ios.infoPlist.RVEndpoint = rvEndpoint;
|
|
231
|
+
}
|
|
223
232
|
// Add location permissions for Geospatial API
|
|
224
233
|
if (geospatialAnchorProvider === "arcore" || geospatialAnchorProvider === "reactvision" || includeARCore === true) {
|
|
225
234
|
config.ios.infoPlist.NSLocationWhenInUseUsageDescription =
|
|
@@ -231,7 +240,7 @@ const withDefaultInfoPlist = (config, _props) => {
|
|
|
231
240
|
};
|
|
232
241
|
exports.withDefaultInfoPlist = withDefaultInfoPlist;
|
|
233
242
|
const withViroIos = (config, props) => {
|
|
234
|
-
(0, config_plugins_1.withPlugins)(config, [[withViroPods, props]]);
|
|
243
|
+
config = (0, config_plugins_1.withPlugins)(config, [[withViroPods, props]]);
|
|
235
244
|
(0, exports.withDefaultInfoPlist)(config, props);
|
|
236
245
|
withEnabledBitcode(config);
|
|
237
246
|
withExcludedSimulatorArchitectures(config);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withViroVisionOS.ts
|
|
3
|
+
*
|
|
4
|
+
* Expo config plugin that adds a visionOS target to your app's Xcode project.
|
|
5
|
+
*
|
|
6
|
+
* What it does:
|
|
7
|
+
* 1. Writes the SwiftUI App entry point (ViroVisionApp.swift) into the target folder
|
|
8
|
+
* 2. Adds a visionOS pod target to the Podfile
|
|
9
|
+
* 3. Creates a visionOS Xcode target in the .pbxproj with correct SDK + build settings
|
|
10
|
+
*
|
|
11
|
+
* Usage in app.json:
|
|
12
|
+
* {
|
|
13
|
+
* "plugins": [
|
|
14
|
+
* ["@reactvision/react-viro", { ... }],
|
|
15
|
+
* "@reactvision/react-viro/plugins/withViroVisionOS"
|
|
16
|
+
* ]
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* After running `expo prebuild`, run `pod install` in ios/ then build the
|
|
20
|
+
* <AppName>Vision target in Xcode targeting the visionOS simulator.
|
|
21
|
+
*/
|
|
22
|
+
import { ConfigPlugin } from "@expo/config-plugins";
|
|
23
|
+
export declare const withViroVisionOS: ConfigPlugin;
|
|
24
|
+
export default withViroVisionOS;
|