From 3e8624562a45ac115f98fe8cb2b03497490c2a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwa=C5=9Bniewski?= Date: Mon, 26 Feb 2024 12:13:58 +0100 Subject: [PATCH] feat: add multi-window support (#117) * feat: add multi-window support * feat: introduce WindowManager fix: RCTReactViewController properly check props to update fix: use clearColor instead of systemBackgroundColor for visionOS (#125) --- .../Libraries/AppDelegate/RCTAppDelegate.h | 4 +- .../Libraries/AppDelegate/RCTAppDelegate.mm | 4 + .../AppDelegate/RCTRootViewFactory.mm | 8 + .../SwiftExtensions/RCTMainWindow.swift | 53 +++- .../SwiftExtensions/RCTReactContext.swift | 45 ++++ .../SwiftExtensions/RCTReactViewController.h | 2 + .../RCTRootViewRepresentable.swift | 6 +- .../Libraries/SwiftExtensions/RCTWindow.swift | 45 ++++ .../React-RCTSwiftExtensions.podspec | 1 + .../WindowManager/NativeWindowManager.js | 8 + .../WindowManager/RCTWindowManager.h | 6 + .../WindowManager/RCTWindowManager.mm | 90 +++++++ .../WindowManager/WindowManager.d.ts | 16 ++ .../Libraries/WindowManager/WindowManager.js | 63 +++++ .../Libraries/XR/ImmersiveBridge.swift | 8 +- .../react-native/Libraries/XR/RCTXRModule.mm | 63 +++-- packages/react-native/Libraries/XR/XR.d.ts | 3 +- packages/react-native/Libraries/XR/XR.js | 14 +- .../__snapshots__/public-api-test.js.snap | 26 +- packages/react-native/React-Core.podspec | 1 + packages/react-native/React.podspec | 1 + packages/react-native/React/Base/RCTUtils.m | 12 +- packages/react-native/index.js | 4 + .../react-native/scripts/react_native_pods.rb | 1 + .../visionos_modules/NativeWindowManager.js | 24 ++ .../specs/visionos_modules/NativeXRModule.js | 9 +- packages/react-native/types/index.d.ts | 1 + packages/rn-tester/Podfile.lock | 226 ++++++++++++++---- .../rn-tester/RNTester-visionOS/App.swift | 8 +- packages/rn-tester/js/RNTesterApp.ios.js | 3 + .../js/examples/SecondWindow/SecondWindow.js | 52 ++++ .../rn-tester/js/examples/XR/XRExample.js | 28 ++- 32 files changed, 734 insertions(+), 101 deletions(-) create mode 100644 packages/react-native/Libraries/SwiftExtensions/RCTReactContext.swift create mode 100644 packages/react-native/Libraries/SwiftExtensions/RCTWindow.swift create mode 100644 packages/react-native/Libraries/WindowManager/NativeWindowManager.js create mode 100644 packages/react-native/Libraries/WindowManager/RCTWindowManager.h create mode 100644 packages/react-native/Libraries/WindowManager/RCTWindowManager.mm create mode 100644 packages/react-native/Libraries/WindowManager/WindowManager.d.ts create mode 100644 packages/react-native/Libraries/WindowManager/WindowManager.js create mode 100644 packages/react-native/src/private/specs/visionos_modules/NativeWindowManager.js create mode 100644 packages/rn-tester/js/examples/SecondWindow/SecondWindow.js diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h index 51ce13d8321922..d3ecacac5754dd 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h @@ -59,7 +59,9 @@ NS_ASSUME_NONNULL_BEGIN /// The window object, used to render the UViewControllers @property (nonatomic, strong, nonnull) UIWindow *window; -@property (nonatomic, nullable) RCTBridge *bridge; +/// Store last focused window to properly handle multi-window scenarios +@property (nonatomic, weak, nullable) UIWindow *lastFocusedWindow; +@property (nonatomic, strong, nullable) RCTBridge *bridge; @property (nonatomic, strong, nullable) NSString *moduleName; @property (nonatomic, strong, nullable) NSDictionary *initialProps; @property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory; diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index c0f0a926126d0f..933410db53919b 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -87,7 +87,11 @@ - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge BOOL enableFabric = self.fabricEnabled; UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, enableFabric); +#if TARGET_OS_VISION + rootView.backgroundColor = [UIColor clearColor]; +#else rootView.backgroundColor = [UIColor systemBackgroundColor]; +#endif return rootView; } diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm index 377e5efeae8030..05c1baa804abc8 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm @@ -149,7 +149,11 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName initWithSurface:surface sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact]; +#if TARGET_OS_VISION + surfaceHostingProxyRootView.backgroundColor = [UIColor clearColor]; +#else surfaceHostingProxyRootView.backgroundColor = [UIColor systemBackgroundColor]; +#endif if (self->_configuration.customizeRootView != nil) { self->_configuration.customizeRootView(surfaceHostingProxyRootView); } @@ -183,7 +187,11 @@ - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge BOOL enableFabric = self->_configuration.fabricEnabled; UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, enableFabric); +#if TARGET_OS_VISION + rootView.backgroundColor = [UIColor clearColor]; +#else rootView.backgroundColor = [UIColor systemBackgroundColor]; +#endif return rootView; } diff --git a/packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift b/packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift index 1bf0bca5901b63..bbf3a3c203252e 100644 --- a/packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift +++ b/packages/react-native/Libraries/SwiftExtensions/RCTMainWindow.swift @@ -15,8 +15,8 @@ import SwiftUI } ``` - Note: If you want to create additional windows in your app, create a new `WindowGroup {}` and pass it a `RCTRootViewRepresentable`. -*/ + Note: If you want to create additional windows in your app, use `RCTWindow()`. + */ public struct RCTMainWindow: Scene { var moduleName: String var initialProps: RCTRootViewRepresentable.InitialPropsType @@ -29,6 +29,55 @@ public struct RCTMainWindow: Scene { public var body: some Scene { WindowGroup { RCTRootViewRepresentable(moduleName: moduleName, initialProps: initialProps) + .modifier(WindowHandlingModifier()) + } + } +} + +/** + Handles data sharing between React Native and SwiftUI views. + */ +struct WindowHandlingModifier: ViewModifier { + typealias UserInfoType = Dictionary + + @Environment(\.reactContext) private var reactContext + @Environment(\.openWindow) private var openWindow + @Environment(\.dismissWindow) private var dismissWindow + @Environment(\.supportsMultipleWindows) private var supportsMultipleWindows + + func body(content: Content) -> some View { + // Attach listeners only if app supports multiple windows + if supportsMultipleWindows { + content + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("RCTOpenWindow"))) { data in + guard let id = data.userInfo?["id"] as? String else { return } + reactContext.scenes.updateValue(RCTSceneData(id: id, props: data.userInfo?["userInfo"] as? UserInfoType), forKey: id) + openWindow(id: id) + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("RCTUpdateWindow"))) { data in + guard + let id = data.userInfo?["id"] as? String, + let userInfo = data.userInfo?["userInfo"] as? UserInfoType else { return } + reactContext.scenes[id]?.props = userInfo + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("RCTDismissWindow"))) { data in + guard let id = data.userInfo?["id"] as? String else { return } + dismissWindow(id: id) + reactContext.scenes.removeValue(forKey: id) + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("RCTOpenImmersiveSpace"))) { data in + guard let id = data.userInfo?["id"] as? String else { return } + reactContext.scenes.updateValue( + RCTSceneData(id: id, props: data.userInfo?["userInfo"] as? UserInfoType), + forKey: id + ) + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("RCTDismissImmersiveSpace"))) { data in + guard let id = data.userInfo?["id"] as? String else { return } + reactContext.scenes.removeValue(forKey: id) + } + } else { + content } } } diff --git a/packages/react-native/Libraries/SwiftExtensions/RCTReactContext.swift b/packages/react-native/Libraries/SwiftExtensions/RCTReactContext.swift new file mode 100644 index 00000000000000..4f5c3b3738b0e7 --- /dev/null +++ b/packages/react-native/Libraries/SwiftExtensions/RCTReactContext.swift @@ -0,0 +1,45 @@ +import SwiftUI +import Observation + +@Observable +public class RCTSceneData: Identifiable { + public var id: String + public var props: Dictionary? + + init(id: String, props: Dictionary?) { + self.id = id + self.props = props + } +} + +extension RCTSceneData: Equatable { + public static func == (lhs: RCTSceneData, rhs: RCTSceneData) -> Bool { + lhs.id == rhs.id && NSDictionary(dictionary: lhs.props ?? [:]).isEqual(to: rhs.props ?? [:]) + } +} + +@Observable +public class RCTReactContext { + public var scenes: Dictionary = [:] + + public func getSceneData(id: String) -> RCTSceneData? { + return scenes[id] + } +} + +extension RCTReactContext: Equatable { + public static func == (lhs: RCTReactContext, rhs: RCTReactContext) -> Bool { + NSDictionary(dictionary: lhs.scenes).isEqual(to: rhs.scenes) + } +} + +public extension EnvironmentValues { + var reactContext: RCTReactContext { + get { self[RCTSceneContextKey.self] } + set { self[RCTSceneContextKey.self] = newValue } + } +} + +private struct RCTSceneContextKey: EnvironmentKey { + static var defaultValue: RCTReactContext = RCTReactContext() +} diff --git a/packages/react-native/Libraries/SwiftExtensions/RCTReactViewController.h b/packages/react-native/Libraries/SwiftExtensions/RCTReactViewController.h index 13de19c37cf0e2..aa77150ce21c92 100644 --- a/packages/react-native/Libraries/SwiftExtensions/RCTReactViewController.h +++ b/packages/react-native/Libraries/SwiftExtensions/RCTReactViewController.h @@ -13,4 +13,6 @@ - (instancetype _Nonnull)initWithModuleName:(NSString *_Nonnull)moduleName initProps:(NSDictionary *_Nullable)initProps; +-(void)updateProps:(NSDictionary *_Nullable)newProps; + @end diff --git a/packages/react-native/Libraries/SwiftExtensions/RCTRootViewRepresentable.swift b/packages/react-native/Libraries/SwiftExtensions/RCTRootViewRepresentable.swift index 6a89db9d08e2d7..8a839541ebb8ea 100644 --- a/packages/react-native/Libraries/SwiftExtensions/RCTRootViewRepresentable.swift +++ b/packages/react-native/Libraries/SwiftExtensions/RCTRootViewRepresentable.swift @@ -22,11 +22,11 @@ public struct RCTRootViewRepresentable: UIViewControllerRepresentable { self.initialProps = initialProps } - public func makeUIViewController(context: Context) -> UIViewController { + public func makeUIViewController(context: Context) -> RCTReactViewController { RCTReactViewController(moduleName: moduleName, initProps: initialProps) } - public func updateUIViewController(_ uiViewController: UIViewController, context: Context) { - // noop + public func updateUIViewController(_ uiViewController: RCTReactViewController, context: Context) { + uiViewController.updateProps(initialProps) } } diff --git a/packages/react-native/Libraries/SwiftExtensions/RCTWindow.swift b/packages/react-native/Libraries/SwiftExtensions/RCTWindow.swift new file mode 100644 index 00000000000000..12bb37085cd1dd --- /dev/null +++ b/packages/react-native/Libraries/SwiftExtensions/RCTWindow.swift @@ -0,0 +1,45 @@ +import SwiftUI +import React + +/** + `RCTWindow` is a SwiftUI struct that returns additional scenes. + + Example usage: + ``` + RCTWindow(id: "SecondWindow", sceneData: reactContext.getSceneData(id: "SecondWindow")) + ``` + */ +public struct RCTWindow : Scene { + var id: String + var sceneData: RCTSceneData? + var moduleName: String + + public init(id: String, moduleName: String, sceneData: RCTSceneData?) { + self.id = id + self.moduleName = moduleName + self.sceneData = sceneData + } + + public var body: some Scene { + WindowGroup(id: id) { + Group { + if let sceneData { + RCTRootViewRepresentable(moduleName: moduleName, initialProps: sceneData.props) + } + } + .onAppear { + if sceneData == nil { + RCTFatal(RCTErrorWithMessage("Passed scene data is nil, make sure to pass sceneContext to RCTWindow() in App.swift")) + } + } + } + } +} + +extension RCTWindow { + public init(id: String, sceneData: RCTSceneData?) { + self.id = id + self.moduleName = id + self.sceneData = sceneData + } +} diff --git a/packages/react-native/Libraries/SwiftExtensions/React-RCTSwiftExtensions.podspec b/packages/react-native/Libraries/SwiftExtensions/React-RCTSwiftExtensions.podspec index 531f1d8172ca7d..9d61b8e4cf31c3 100644 --- a/packages/react-native/Libraries/SwiftExtensions/React-RCTSwiftExtensions.podspec +++ b/packages/react-native/Libraries/SwiftExtensions/React-RCTSwiftExtensions.podspec @@ -25,4 +25,5 @@ Pod::Spec.new do |s| s.dependency "React-Core" s.dependency "React-RCTXR" + s.dependency "React-RCTWindowManager" end diff --git a/packages/react-native/Libraries/WindowManager/NativeWindowManager.js b/packages/react-native/Libraries/WindowManager/NativeWindowManager.js new file mode 100644 index 00000000000000..a1904df0228866 --- /dev/null +++ b/packages/react-native/Libraries/WindowManager/NativeWindowManager.js @@ -0,0 +1,8 @@ +/** + * @flow strict + * @format + */ + +export * from '../../src/private/specs/visionos_modules/NativeWindowManager'; +import NativeWindowManager from '../../src/private/specs/visionos_modules/NativeWindowManager'; +export default NativeWindowManager; diff --git a/packages/react-native/Libraries/WindowManager/RCTWindowManager.h b/packages/react-native/Libraries/WindowManager/RCTWindowManager.h new file mode 100644 index 00000000000000..8d782b1bfe77e7 --- /dev/null +++ b/packages/react-native/Libraries/WindowManager/RCTWindowManager.h @@ -0,0 +1,6 @@ +#import +#import + +@interface RCTWindowManager : NSObject + +@end diff --git a/packages/react-native/Libraries/WindowManager/RCTWindowManager.mm b/packages/react-native/Libraries/WindowManager/RCTWindowManager.mm new file mode 100644 index 00000000000000..3700bceba20b95 --- /dev/null +++ b/packages/react-native/Libraries/WindowManager/RCTWindowManager.mm @@ -0,0 +1,90 @@ +#import + +#import + +#import +#import +#import + +// Events +static NSString *const RCTOpenWindow = @"RCTOpenWindow"; +static NSString *const RCTDismissWindow = @"RCTDismissWindow"; +static NSString *const RCTUpdateWindow = @"RCTUpdateWindow"; + +@interface RCTWindowManager () +@end + +@implementation RCTWindowManager + +RCT_EXPORT_MODULE(WindowManager) + +RCT_EXPORT_METHOD(openWindow + : (NSString *)windowId userInfo + : (NSDictionary *)userInfo resolve + : (RCTPromiseResolveBlock)resolve reject + : (RCTPromiseRejectBlock)reject) +{ + RCTExecuteOnMainQueue(^{ + if (!RCTSharedApplication().supportsMultipleScenes) { + reject(@"ERROR", @"Multiple scenes not supported", nil); + } + NSMutableDictionary *userInfoDict = [[NSMutableDictionary alloc] init]; + [userInfoDict setValue:windowId forKey:@"id"]; + if (userInfo != nil) { + [userInfoDict setValue:userInfo forKey:@"userInfo"]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:RCTOpenWindow object:self userInfo:userInfoDict]; + resolve(nil); + }); +} + +RCT_EXPORT_METHOD(closeWindow + : (NSString *)windowId resolve + : (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +{ + RCTExecuteOnMainQueue(^{ + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDismissWindow object:self userInfo:@{@"id": windowId}]; + resolve(nil); + }); +} + +RCT_EXPORT_METHOD(updateWindow + : (NSString *)windowId userInfo + : (NSDictionary *)userInfo resolve + : (RCTPromiseResolveBlock)resolve reject + : (RCTPromiseRejectBlock)reject) +{ + RCTExecuteOnMainQueue(^{ + if (!RCTSharedApplication().supportsMultipleScenes) { + reject(@"ERROR", @"Multiple scenes not supported", nil); + } + NSMutableDictionary *userInfoDict = [[NSMutableDictionary alloc] init]; + [userInfoDict setValue:windowId forKey:@"id"]; + if (userInfo != nil) { + [userInfoDict setValue:userInfo forKey:@"userInfo"]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:RCTUpdateWindow object:self userInfo:userInfoDict]; + resolve(nil); + }); +} + +- (facebook::react::ModuleConstants)constantsToExport { + return [self getConstants]; +} + +- (facebook::react::ModuleConstants)getConstants { + __block facebook::react::ModuleConstants constants; + RCTUnsafeExecuteOnMainQueueSync(^{ + constants = facebook::react::typedConstants({ + .supportsMultipleScenes = RCTSharedApplication().supportsMultipleScenes + }); + }); + + return constants; +} + +- (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); +} + +@end diff --git a/packages/react-native/Libraries/WindowManager/WindowManager.d.ts b/packages/react-native/Libraries/WindowManager/WindowManager.d.ts new file mode 100644 index 00000000000000..bcdf96c272c21c --- /dev/null +++ b/packages/react-native/Libraries/WindowManager/WindowManager.d.ts @@ -0,0 +1,16 @@ +export interface WindowStatic { + id: String; + open (props?: Object): Promise; + update (props: Object): Promise; + close (): Promise; +} + +export interface WindowManagerStatic { + getWindow(id: String): Window; + supportsMultipleScenes: boolean; +} + +export const WindowManager: WindowManagerStatic; +export type WindowManager = WindowManagerStatic; +export const Window: WindowStatic; +export type Window = WindowStatic; diff --git a/packages/react-native/Libraries/WindowManager/WindowManager.js b/packages/react-native/Libraries/WindowManager/WindowManager.js new file mode 100644 index 00000000000000..bb39535a65bb1a --- /dev/null +++ b/packages/react-native/Libraries/WindowManager/WindowManager.js @@ -0,0 +1,63 @@ +/** + * @format + * @flow strict + * @jsdoc + */ + +import NativeWindowManager from './NativeWindowManager'; + +const WindowManager = { + getWindow: function (id: string): Window { + return new Window(id); + }, + + // $FlowIgnore[unsafe-getters-setters] + get supportsMultipleScenes(): boolean { + if (NativeWindowManager == null) { + return false; + } + + const nativeConstants = NativeWindowManager.getConstants(); + return nativeConstants.supportsMultipleScenes || false; + }, +}; + +class Window { + id: string; + + constructor(id: string) { + this.id = id; + } + + // $FlowIgnore[unclear-type] + open(props: ?Object): Promise { + if (NativeWindowManager != null && NativeWindowManager.openWindow != null) { + return NativeWindowManager.openWindow(this.id, props); + } + return Promise.reject(new Error('NativeWindowManager is not available')); + } + + // $FlowIgnore[unclear-type] + close(): Promise { + if ( + NativeWindowManager != null && + NativeWindowManager.closeWindow != null + ) { + return NativeWindowManager.closeWindow(this.id); + } + return Promise.reject(new Error('NativeWindowManager is not available')); + } + + // $FlowIgnore[unclear-type] + update(props: ?Object): Promise { + if ( + NativeWindowManager != null && + NativeWindowManager.updateWindow != null + ) { + return NativeWindowManager.updateWindow(this.id, props); + } + return Promise.reject(new Error('NativeWindowManager is not available')); + } +} + +module.exports = WindowManager; diff --git a/packages/react-native/Libraries/XR/ImmersiveBridge.swift b/packages/react-native/Libraries/XR/ImmersiveBridge.swift index 45fdbcf4087b00..43bdfe5b170739 100644 --- a/packages/react-native/Libraries/XR/ImmersiveBridge.swift +++ b/packages/react-native/Libraries/XR/ImmersiveBridge.swift @@ -9,6 +9,7 @@ import SwiftUI public typealias CompletionHandlerType = (_ result: ImmersiveSpaceResult) -> Void +#if os(visionOS) /** * Utility view used to bridge the gap between SwiftUI environment and UIKit. * @@ -44,12 +45,17 @@ struct ImmersiveBridgeView: View { } } } +#endif -@objc public class ImmersiveBridgeFactory: NSObject { +@objc public class SwiftUIBridgeFactory: NSObject { @objc public static func makeImmersiveBridgeView( spaceId: String, completionHandler: @escaping CompletionHandlerType ) -> UIViewController { +#if os(visionOS) return UIHostingController(rootView: ImmersiveBridgeView(spaceId: spaceId, completionHandler: completionHandler)) +#else + return UIViewController() +#endif } } diff --git a/packages/react-native/Libraries/XR/RCTXRModule.mm b/packages/react-native/Libraries/XR/RCTXRModule.mm index 8b5db6c7cbd008..f0c600b3a09c99 100644 --- a/packages/react-native/Libraries/XR/RCTXRModule.mm +++ b/packages/react-native/Libraries/XR/RCTXRModule.mm @@ -7,11 +7,16 @@ #import #import "RCTXR-Swift.h" +// Events +static NSString *const RCTOpenImmersiveSpace = @"RCTOpenImmersiveSpace"; +static NSString *const RCTDismissImmersiveSpace = @"RCTDismissImmersiveSpace"; + @interface RCTXRModule () @end @implementation RCTXRModule { UIViewController *_immersiveBridgeView; + NSString *_currentSessionId; } RCT_EXPORT_MODULE() @@ -20,28 +25,51 @@ @implementation RCTXRModule { : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) { - [self removeImmersiveBridge]; + [self removeViewController:self->_immersiveBridgeView]; + self->_immersiveBridgeView = nil; + RCTExecuteOnMainQueue(^{ + if (self->_currentSessionId != nil) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDismissImmersiveSpace object:self userInfo:@{@"id": self->_currentSessionId}]; + } + }); + _currentSessionId = nil; resolve(nil); } RCT_EXPORT_METHOD(requestSession - : (NSString *)sessionId resolve - : (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + : (NSString *)sessionId userInfo + : (NSDictionary *)userInfo resolve + : (RCTPromiseResolveBlock)resolve reject + : (RCTPromiseRejectBlock)reject) { RCTExecuteOnMainQueue(^{ + if (!RCTSharedApplication().supportsMultipleScenes) { + reject(@"ERROR", @"Multiple scenes not supported", nil); + } UIWindow *keyWindow = RCTKeyWindow(); UIViewController *rootViewController = keyWindow.rootViewController; if (self->_immersiveBridgeView == nil) { - self->_immersiveBridgeView = [ImmersiveBridgeFactory makeImmersiveBridgeViewWithSpaceId:sessionId + NSMutableDictionary *userInfoDict = [[NSMutableDictionary alloc] init]; + [userInfoDict setValue:sessionId forKey:@"id"]; + if (userInfo != nil) { + [userInfoDict setValue:userInfo forKey:@"userInfo"]; + } + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter postNotificationName:RCTOpenImmersiveSpace object:self userInfo:userInfoDict]; + self->_currentSessionId = sessionId; + + self->_immersiveBridgeView = [SwiftUIBridgeFactory makeImmersiveBridgeViewWithSpaceId:sessionId completionHandler:^(enum ImmersiveSpaceResult result){ if (result == ImmersiveSpaceResultError) { reject(@"ERROR", @"Immersive Space failed to open, the system cannot fulfill the request.", nil); - [self removeImmersiveBridge]; + [self removeViewController:self->_immersiveBridgeView]; + self->_immersiveBridgeView = nil; } else if (result == ImmersiveSpaceResultUserCancelled) { reject(@"ERROR", @"Immersive Space canceled by user", nil); - [self removeImmersiveBridge]; + [self removeViewController:self->_immersiveBridgeView]; + self->_immersiveBridgeView = nil; } else if (result == ImmersiveSpaceResultOpened) { resolve(nil); } @@ -56,28 +84,13 @@ @implementation RCTXRModule { }); } -- (facebook::react::ModuleConstants)constantsToExport { - return [self getConstants]; -} - -- (facebook::react::ModuleConstants)getConstants { - __block facebook::react::ModuleConstants constants; - RCTUnsafeExecuteOnMainQueueSync(^{ - constants = facebook::react::typedConstants({ - .supportsMultipleScenes = RCTSharedApplication().supportsMultipleScenes - }); - }); - - return constants; -} -- (void) removeImmersiveBridge +- (void)removeViewController:(UIViewController*)viewController { RCTExecuteOnMainQueue(^{ - [self->_immersiveBridgeView willMoveToParentViewController:nil]; - [self->_immersiveBridgeView.view removeFromSuperview]; - [self->_immersiveBridgeView removeFromParentViewController]; - self->_immersiveBridgeView = nil; + [viewController willMoveToParentViewController:nil]; + [viewController.view removeFromSuperview]; + [viewController removeFromParentViewController]; }); } diff --git a/packages/react-native/Libraries/XR/XR.d.ts b/packages/react-native/Libraries/XR/XR.d.ts index 11d397a4ebde34..293a97bb084c3e 100644 --- a/packages/react-native/Libraries/XR/XR.d.ts +++ b/packages/react-native/Libraries/XR/XR.d.ts @@ -1,8 +1,7 @@ export interface XRStatic { - requestSession(sessionId: string): Promise; + requestSession(sessionId: string, userInfo: Object): Promise; endSession(): Promise; - supportsMultipleScenes: boolean; } export const XR: XRStatic; diff --git a/packages/react-native/Libraries/XR/XR.js b/packages/react-native/Libraries/XR/XR.js index 3487429aff1bb5..923a3184c71dea 100644 --- a/packages/react-native/Libraries/XR/XR.js +++ b/packages/react-native/Libraries/XR/XR.js @@ -7,9 +7,10 @@ import NativeXRModule from './NativeXRModule'; const XR = { - requestSession: (sessionId?: string): Promise => { + // $FlowIgnore[unclear-type] + requestSession: (sessionId: string, userInfo: ?Object): Promise => { if (NativeXRModule != null && NativeXRModule.requestSession != null) { - return NativeXRModule.requestSession(sessionId); + return NativeXRModule.requestSession(sessionId, userInfo); } return Promise.reject(new Error('NativeXRModule is not available')); }, @@ -19,15 +20,6 @@ const XR = { } return Promise.reject(new Error('NativeXRModule is not available')); }, - // $FlowIgnore[unsafe-getters-setters] - get supportsMultipleScenes(): boolean { - if (NativeXRModule == null) { - return false; - } - - const nativeConstants = NativeXRModule.getConstants(); - return nativeConstants.supportsMultipleScenes || false; - }, }; module.exports = XR; diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index bb68de6d91fa12..0600ccb9ac9478 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -9734,6 +9734,28 @@ exports[`public API should not change unintentionally Libraries/WebSocket/WebSoc exports[`public API should not change unintentionally Libraries/WebSocket/WebSocketInterceptor.js 1`] = `"UNTYPED MODULE"`; +exports[`public API should not change unintentionally Libraries/WindowManager/NativeWindowManager.js 1`] = ` +"export * from \\"../../src/private/specs/visionos_modules/NativeWindowManager\\"; +declare export default typeof NativeWindowManager; +" +`; + +exports[`public API should not change unintentionally Libraries/WindowManager/WindowManager.js 1`] = ` +"declare const WindowManager: { + getWindow: (id: string) => Window, + get supportsMultipleScenes(): boolean, +}; +declare class Window { + id: string; + constructor(id: string): void; + open(props: ?Object): Promise; + close(): Promise; + update(props: ?Object): Promise; +} +declare module.exports: WindowManager; +" +`; + exports[`public API should not change unintentionally Libraries/XR/NativeXRModule.js 1`] = ` "export * from \\"../../src/private/specs/visionos_modules/NativeXRModule\\"; declare export default typeof NativeXRModule; @@ -9742,9 +9764,8 @@ declare export default typeof NativeXRModule; exports[`public API should not change unintentionally Libraries/XR/XR.js 1`] = ` "declare const XR: { - requestSession: (sessionId?: string) => Promise, + requestSession: (sessionId: string, userInfo: ?Object) => Promise, endSession: () => Promise, - get supportsMultipleScenes(): boolean, }; declare module.exports: XR; " @@ -9869,6 +9890,7 @@ declare module.exports: { get Settings(): Settings, get Share(): Share, get XR(): XR, + get WindowManager(): WindowManager, get StyleSheet(): StyleSheet, get Systrace(): Systrace, get ToastAndroid(): ToastAndroid, diff --git a/packages/react-native/React-Core.podspec b/packages/react-native/React-Core.podspec index 2205f112421ef1..0338fa1072d4f5 100644 --- a/packages/react-native/React-Core.podspec +++ b/packages/react-native/React-Core.podspec @@ -38,6 +38,7 @@ header_subspecs = { 'RCTTextHeaders' => 'Libraries/Text/**/*.h', 'RCTVibrationHeaders' => 'Libraries/Vibration/*.h', 'RCTXRHeaders' => 'Libraries/XR/*.h', + 'RCTWindowManagerHeaders' => 'Libraries/WindowManager/*.h', } frameworks_search_paths = [] diff --git a/packages/react-native/React.podspec b/packages/react-native/React.podspec index 2d085e63040906..50397b11182021 100644 --- a/packages/react-native/React.podspec +++ b/packages/react-native/React.podspec @@ -54,4 +54,5 @@ Pod::Spec.new do |s| s.dependency "React-RCTText", version s.dependency "React-RCTVibration", version s.dependency "React-RCTXR", version + s.dependency "React-RCTWindowManager", version end diff --git a/packages/react-native/React/Base/RCTUtils.m b/packages/react-native/React/Base/RCTUtils.m index 1573a675f23a6b..5f1e13390b37c7 100644 --- a/packages/react-native/React/Base/RCTUtils.m +++ b/packages/react-native/React/Base/RCTUtils.m @@ -566,7 +566,17 @@ BOOL RCTRunningInAppExtension(void) if (RCTRunningInAppExtension()) { return nil; } - + + id delegate = RCTSharedApplication().delegate; + + SEL lastFocusedWindowSelector = NSSelectorFromString(@"lastFocusedWindow"); + if ([delegate respondsToSelector:lastFocusedWindowSelector]) { + UIWindow *lastFocusedWindow = [delegate performSelector:lastFocusedWindowSelector]; + if (lastFocusedWindow) { + return lastFocusedWindow; + } + } + NSSet *connectedScenes = RCTSharedApplication().connectedScenes; UIScene *foregroundActiveScene; diff --git a/packages/react-native/index.js b/packages/react-native/index.js index 22166d599c1370..02fb78b62b27d3 100644 --- a/packages/react-native/index.js +++ b/packages/react-native/index.js @@ -91,6 +91,7 @@ import typeof Platform from './Libraries/Utilities/Platform'; import typeof useColorScheme from './Libraries/Utilities/useColorScheme'; import typeof useWindowDimensions from './Libraries/Utilities/useWindowDimensions'; import typeof Vibration from './Libraries/Vibration/Vibration'; +import typeof WindowManager from './Libraries/WindowManager/WindowManager'; import typeof XR from './Libraries/XR/XR'; import typeof YellowBox from './Libraries/YellowBox/YellowBoxDeprecated'; @@ -305,6 +306,9 @@ module.exports = { get XR(): XR { return require('./Libraries/XR/XR'); }, + get WindowManager(): WindowManager { + return require('./Libraries/WindowManager/WindowManager'); + }, get StyleSheet(): StyleSheet { return require('./Libraries/StyleSheet/StyleSheet'); }, diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 91d77ea446f27f..094f473b5c1bd8 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -136,6 +136,7 @@ def use_react_native! ( pod 'RCTDeprecation', :path => "#{prefix}/ReactApple/Libraries/RCTFoundation/RCTDeprecation" pod 'React-RCTSwiftExtensions', :path => "#{prefix}/Libraries/SwiftExtensions" pod 'React-RCTXR', :path => "#{prefix}/Libraries/XR" + pod 'React-RCTWindowManager', :path => "#{prefix}/Libraries/WindowManager", :modular_headers => true if hermes_enabled setup_hermes!(:react_native_path => prefix) diff --git a/packages/react-native/src/private/specs/visionos_modules/NativeWindowManager.js b/packages/react-native/src/private/specs/visionos_modules/NativeWindowManager.js new file mode 100644 index 00000000000000..db0332702236c4 --- /dev/null +++ b/packages/react-native/src/private/specs/visionos_modules/NativeWindowManager.js @@ -0,0 +1,24 @@ +/** + * @flow strict + * @format + */ + +import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport'; + +import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry'; + +export type WindowManagerConstants = {| + +supportsMultipleScenes?: boolean, +|}; + +export interface Spec extends TurboModule { + +getConstants: () => WindowManagerConstants; + + // $FlowIgnore[unclear-type] + +openWindow: (windowId: string, userInfo: Object) => Promise; + // $FlowIgnore[unclear-type] + +updateWindow: (windowId: string, userInfo: Object) => Promise; + +closeWindow: (windowId: string) => Promise; +} + +export default (TurboModuleRegistry.get('WindowManager'): ?Spec); diff --git a/packages/react-native/src/private/specs/visionos_modules/NativeXRModule.js b/packages/react-native/src/private/specs/visionos_modules/NativeXRModule.js index ce8d22dca68c49..f96c2a84a48ac4 100644 --- a/packages/react-native/src/private/specs/visionos_modules/NativeXRModule.js +++ b/packages/react-native/src/private/specs/visionos_modules/NativeXRModule.js @@ -7,14 +7,9 @@ import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry'; -export type XRModuleConstants = {| - +supportsMultipleScenes?: boolean, -|}; - export interface Spec extends TurboModule { - +getConstants: () => XRModuleConstants; - - +requestSession: (sessionId?: string) => Promise; + // $FlowIgnore[unclear-type] + +requestSession: (sessionId?: string, userInfo: Object) => Promise; +endSession: () => Promise; } diff --git a/packages/react-native/types/index.d.ts b/packages/react-native/types/index.d.ts index 917b5cc65d098d..5c89c19b97c707 100644 --- a/packages/react-native/types/index.d.ts +++ b/packages/react-native/types/index.d.ts @@ -148,6 +148,7 @@ export * from '../Libraries/Utilities/PixelRatio'; export * from '../Libraries/Utilities/Platform'; export * from '../Libraries/Vibration/Vibration'; export * from '../Libraries/XR/XR'; +export * from '../Libraries/WindowManager/WindowManager'; export * from '../Libraries/YellowBox/YellowBoxDeprecated'; export * from '../Libraries/vendor/core/ErrorUtils'; export { diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 31d96ad15e4c26..921d8b4b03a869 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -5,10 +5,12 @@ PODS: - fmt (9.1.0) - glog (0.3.5) - hermes-engine (1000.0.0): + - hermes-engine/cdp (= 1000.0.0) - hermes-engine/Hermes (= 1000.0.0) - hermes-engine/inspector (= 1000.0.0) - hermes-engine/inspector_chrome (= 1000.0.0) - hermes-engine/Public (= 1000.0.0) + - hermes-engine/cdp (1000.0.0) - hermes-engine/Hermes (1000.0.0) - hermes-engine/inspector (1000.0.0) - hermes-engine/inspector_chrome (1000.0.0) @@ -20,16 +22,17 @@ PODS: - RCT-Folly (= 2024.01.01.00) - RCTRequired - RCTTypeSafety - - React-Codegen - React-Core - React-debug - React-Fabric + - React-featureflags - React-graphics - React-ImageManager - React-NativeModulesApple - React-RCTFabric - React-rendererdebug - React-utils + - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga @@ -62,16 +65,17 @@ PODS: - RCT-Folly (= 2024.01.01.00) - RCTRequired - RCTTypeSafety - - React-Codegen - React-Core - React-debug - React-Fabric + - React-featureflags - React-graphics - React-ImageManager - React-NativeModulesApple - React-RCTFabric - React-rendererdebug - React-utils + - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga @@ -110,27 +114,9 @@ PODS: - React-RCTSettings (= 1000.0.0) - React-RCTText (= 1000.0.0) - React-RCTVibration (= 1000.0.0) + - React-RCTWindowManager (= 1000.0.0) - React-RCTXR (= 1000.0.0) - React-callinvoker (1000.0.0) - - React-Codegen (1000.0.0): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-FabricImage - - React-graphics - - React-jsi - - React-jsiexecutor - - React-NativeModulesApple - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - React-Core (1000.0.0): - glog - hermes-engine @@ -138,9 +124,11 @@ PODS: - RCTDeprecation - React-Core/Default (= 1000.0.0) - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -153,9 +141,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -167,10 +157,11 @@ PODS: - RCT-Folly (= 2024.01.01.00) - RCTDeprecation - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor - - React-jsinspector (= 1000.0.0) + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -184,10 +175,11 @@ PODS: - React-Core/Default (= 1000.0.0) - React-Core/RCTWebSocket (= 1000.0.0) - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor - - React-jsinspector (= 1000.0.0) + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -200,9 +192,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -215,9 +209,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -230,9 +226,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -245,9 +243,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -260,9 +260,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -275,9 +277,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -290,9 +294,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -305,9 +311,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -320,9 +328,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -335,9 +345,11 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -350,9 +362,28 @@ PODS: - RCTDeprecation - React-Core/Default (= 1000.0.0) - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0.1) + - Yoga + - React-Core/RCTWindowManagerHeaders (1000.0.0): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils @@ -365,23 +396,28 @@ PODS: - RCTDeprecation - React-Core/Default - React-cxxreact + - React-featureflags - React-hermes - React-jsi - React-jsiexecutor + - React-jsinspector - React-perflogger - React-runtimescheduler - React-utils - SocketRocket (= 0.7.0.1) - Yoga - React-CoreModules (1000.0.0): + - DoubleConversion + - fmt (= 9.1.0) - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety (= 1000.0.0) - - React-Codegen - React-Core/CoreModulesHeaders (= 1000.0.0) - React-jsi (= 1000.0.0) + - React-jsinspector - React-NativeModulesApple - React-RCTBlob - React-RCTImage (= 1000.0.0) + - ReactCodegen - ReactCommon - SocketRocket (= 0.7.0.1) - React-cxxreact (1000.0.0): @@ -394,7 +430,7 @@ PODS: - React-callinvoker (= 1000.0.0) - React-debug (= 1000.0.0) - React-jsi (= 1000.0.0) - - React-jsinspector (= 1000.0.0) + - React-jsinspector - React-logger (= 1000.0.0) - React-perflogger (= 1000.0.0) - React-runtimeexecutor (= 1000.0.0) @@ -472,6 +508,7 @@ PODS: - React-Fabric/telemetry (= 1000.0.0) - React-Fabric/templateprocessor (= 1000.0.0) - React-Fabric/uimanager (= 1000.0.0) + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -491,6 +528,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -510,6 +548,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -529,6 +568,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -548,6 +588,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -570,6 +611,7 @@ PODS: - React-Fabric/components/legacyviewmanagerinterop (= 1000.0.0) - React-Fabric/components/root (= 1000.0.0) - React-Fabric/components/view (= 1000.0.0) + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -589,6 +631,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -608,6 +651,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -627,6 +671,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -647,6 +692,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -686,6 +732,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -705,6 +752,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -724,6 +772,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -764,6 +813,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -805,6 +855,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -824,6 +875,7 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags - React-graphics - React-jsi - React-jsiexecutor @@ -1180,10 +1232,35 @@ PODS: - React-utils - ReactCommon - Yoga + - React-featureflags (1000.0.0) + - React-featureflagsnativemodule (1000.0.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - React-graphics (1000.0.0): + - DoubleConversion + - fmt (= 9.1.0) - glog - RCT-Folly/Fabric (= 2024.01.01.00) - - React-Core/Default (= 1000.0.0) + - React-jsi + - React-jsiexecutor - React-utils - React-hermes (1000.0.0): - DoubleConversion @@ -1194,7 +1271,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi - React-jsiexecutor (= 1000.0.0) - - React-jsinspector (= 1000.0.0) + - React-jsinspector - React-perflogger (= 1000.0.0) - React-runtimeexecutor - React-idlecallbacksnativemodule (1000.0.0): @@ -1247,10 +1324,12 @@ PODS: - RCT-Folly (= 2024.01.01.00) - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) + - React-jsinspector - React-perflogger (= 1000.0.0) - React-jsinspector (1000.0.0): - DoubleConversion - glog + - hermes-engine - RCT-Folly (= 2024.01.01.00) - React-featureflags - React-jsi @@ -1263,6 +1342,27 @@ PODS: - React-Mapbuffer (1000.0.0): - glog - React-debug + - React-microtasksnativemodule (1000.0.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - React-nativeconfig (1000.0.0) - React-NativeModulesApple (1000.0.0): - glog @@ -1271,6 +1371,7 @@ PODS: - React-Core - React-cxxreact - React-jsi + - React-jsinspector - React-runtimeexecutor - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core @@ -1285,10 +1386,10 @@ PODS: - React-RCTAnimation (1000.0.0): - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety - - React-Codegen - React-Core/RCTAnimationHeaders - React-jsi - React-NativeModulesApple + - ReactCodegen - ReactCommon - React-RCTAppDelegate (1000.0.0): - RCT-Folly (= 2024.01.01.00) @@ -1313,16 +1414,20 @@ PODS: - React-RuntimeHermes - React-runtimescheduler - React-utils + - ReactCodegen - ReactCommon - React-RCTBlob (1000.0.0): + - DoubleConversion + - fmt (= 9.1.0) - hermes-engine - RCT-Folly (= 2024.01.01.00) - - React-Codegen - React-Core/RCTBlobHeaders - React-Core/RCTWebSocket - React-jsi + - React-jsinspector - React-NativeModulesApple - React-RCTNetwork + - ReactCodegen - ReactCommon - React-RCTFabric (1000.0.0): - glog @@ -1333,9 +1438,11 @@ PODS: - React-Fabric - React-FabricComponents - React-FabricImage + - React-featureflags - React-graphics - React-ImageManager - React-jsi + - React-jsinspector - React-nativeconfig - React-performancetimeline - React-RCTImage @@ -1348,44 +1455,45 @@ PODS: - React-RCTImage (1000.0.0): - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety - - React-Codegen - React-Core/RCTImageHeaders - React-jsi - React-NativeModulesApple - React-RCTNetwork + - ReactCodegen - ReactCommon - React-RCTLinking (1000.0.0): - - React-Codegen - React-Core/RCTLinkingHeaders (= 1000.0.0) - React-jsi (= 1000.0.0) - React-NativeModulesApple + - ReactCodegen - ReactCommon - ReactCommon/turbomodule/core (= 1000.0.0) - React-RCTNetwork (1000.0.0): - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety - - React-Codegen - React-Core/RCTNetworkHeaders - React-jsi - React-NativeModulesApple + - ReactCodegen - ReactCommon - React-RCTPushNotification (1000.0.0): - RCTTypeSafety - - React-Codegen - React-Core/RCTPushNotificationHeaders - React-jsi - React-NativeModulesApple + - ReactCodegen - ReactCommon - React-RCTSettings (1000.0.0): - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety - - React-Codegen - React-Core/RCTSettingsHeaders - React-jsi - React-NativeModulesApple + - ReactCodegen - ReactCommon - React-RCTSwiftExtensions (1000.0.0): - React-Core + - React-RCTWindowManager - React-RCTXR - React-RCTTest (1000.0.0): - RCT-Folly (= 2024.01.01.00) @@ -1398,7 +1506,6 @@ PODS: - Yoga - React-RCTVibration (1000.0.0): - RCT-Folly (= 2024.01.01.00) - - React-Codegen - React-Core/RCTVibrationHeaders - React-jsi - React-NativeModulesApple @@ -1435,6 +1542,7 @@ PODS: - hermes-engine - RCT-Folly/Fabric (= 2024.01.01.00) - React-cxxreact + - React-featureflags - React-jserrorhandler - React-jsi - React-jsiexecutor @@ -1447,7 +1555,10 @@ PODS: - React-RuntimeHermes (1000.0.0): - hermes-engine - RCT-Folly/Fabric (= 2024.01.01.00) + - React-featureflags + - React-hermes - React-jsi + - React-jsinspector - React-jsitracing - React-nativeconfig - React-RuntimeCore @@ -1459,6 +1570,7 @@ PODS: - React-callinvoker - React-cxxreact - React-debug + - React-featureflags - React-jsi - React-rendererconsistency - React-rendererdebug @@ -1466,21 +1578,42 @@ PODS: - React-utils - React-utils (1000.0.0): - glog + - hermes-engine - RCT-Folly (= 2024.01.01.00) - React-debug + - React-jsi (= 1000.0.0) + - ReactCodegen (1000.0.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core - ReactCommon (1000.0.0): - - React-logger (= 1000.0.0) - ReactCommon/turbomodule (= 1000.0.0) - ReactCommon-Samples (1000.0.0): - DoubleConversion - fmt (= 9.1.0) - hermes-engine - RCT-Folly - - React-Codegen - React-Core - React-cxxreact - React-jsi - React-NativeModulesApple + - ReactCodegen - ReactCommon - ReactCommon/turbomodule (1000.0.0): - DoubleConversion @@ -1515,6 +1648,7 @@ PODS: - React-callinvoker (= 1000.0.0) - React-cxxreact (= 1000.0.0) - React-debug (= 1000.0.0) + - React-featureflags (= 1000.0.0) - React-jsi (= 1000.0.0) - React-logger (= 1000.0.0) - React-perflogger (= 1000.0.0) @@ -1526,16 +1660,17 @@ PODS: - RCT-Folly (= 2024.01.01.00) - RCTRequired - RCTTypeSafety - - React-Codegen - React-Core - React-debug - React-Fabric + - React-featureflags - React-graphics - React-ImageManager - React-NativeModulesApple - React-RCTFabric - React-rendererdebug - React-utils + - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga @@ -1560,7 +1695,6 @@ DEPENDENCIES: - RCTTypeSafety (from `../react-native/Libraries/TypeSafety`) - React (from `../react-native/`) - React-callinvoker (from `../react-native/ReactCommon/callinvoker`) - - React-Codegen (from `build/generated/ios`) - React-Core (from `../react-native/`) - React-Core/RCTWebSocket (from `../react-native/`) - React-CoreModules (from `../react-native/React/CoreModules`) @@ -1571,6 +1705,8 @@ DEPENDENCIES: - React-Fabric (from `../react-native/ReactCommon`) - React-FabricComponents (from `../react-native/ReactCommon`) - React-FabricImage (from `../react-native/ReactCommon`) + - React-featureflags (from `../react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../react-native/ReactCommon/react/nativemodule/featureflags`) - React-graphics (from `../react-native/ReactCommon/react/renderer/graphics`) - React-hermes (from `../react-native/ReactCommon/hermes`) - React-idlecallbacksnativemodule (from `../react-native/ReactCommon/react/nativemodule/idlecallbacks`) @@ -1582,6 +1718,7 @@ DEPENDENCIES: - React-jsitracing (from `../react-native/ReactCommon/hermes/executor/`) - React-logger (from `../react-native/ReactCommon/logger`) - React-Mapbuffer (from `../react-native/ReactCommon`) + - React-microtasksnativemodule (from `../react-native/ReactCommon/react/nativemodule/microtasks`) - React-nativeconfig (from `../react-native/ReactCommon`) - React-NativeModulesApple (from `../react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-perflogger (from `../react-native/ReactCommon/reactperflogger`) @@ -1609,6 +1746,7 @@ DEPENDENCIES: - React-RuntimeHermes (from `../react-native/ReactCommon/react/runtime`) - React-runtimescheduler (from `../react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../react-native/ReactCommon/react/utils`) + - ReactCodegen (from `build/generated/ios`) - ReactCommon-Samples (from `../react-native/ReactCommon/react/nativemodule/samples`) - ReactCommon/turbomodule/core (from `../react-native/ReactCommon`) - ScreenshotManager (from `NativeModuleExample`) @@ -1647,8 +1785,6 @@ EXTERNAL SOURCES: :path: "../react-native/" React-callinvoker: :path: "../react-native/ReactCommon/callinvoker" - React-Codegen: - :path: build/generated/ios React-Core: :path: "../react-native/" React-CoreModules: @@ -1667,6 +1803,10 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon" React-FabricImage: :path: "../react-native/ReactCommon" + React-featureflags: + :path: "../react-native/ReactCommon/react/featureflags" + React-featureflagsnativemodule: + :path: "../react-native/ReactCommon/react/nativemodule/featureflags" React-graphics: :path: "../react-native/ReactCommon/react/renderer/graphics" React-hermes: @@ -1689,6 +1829,8 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon/logger" React-Mapbuffer: :path: "../react-native/ReactCommon" + React-microtasksnativemodule: + :path: "../react-native/ReactCommon/react/nativemodule/microtasks" React-nativeconfig: :path: "../react-native/ReactCommon" React-NativeModulesApple: @@ -1743,6 +1885,8 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon/react/renderer/runtimescheduler" React-utils: :path: "../react-native/ReactCommon/react/utils" + ReactCodegen: + :path: build/generated/ios ReactCommon: :path: "../react-native/ReactCommon" ReactCommon-Samples: diff --git a/packages/rn-tester/RNTester-visionOS/App.swift b/packages/rn-tester/RNTester-visionOS/App.swift index 8e28482981e8a6..939cef2664d031 100644 --- a/packages/rn-tester/RNTester-visionOS/App.swift +++ b/packages/rn-tester/RNTester-visionOS/App.swift @@ -5,11 +5,17 @@ import React_RCTSwiftExtensions @main struct RNTesterApp: App { @UIApplicationDelegateAdaptor var delegate: AppDelegate + @Environment(\.reactContext) private var reactContext + @State private var immersionLevel: ImmersionStyle = .full var body: some Scene { RCTMainWindow(moduleName: "RNTesterApp") + + RCTWindow(id: "SecondWindow", sceneData: reactContext.getSceneData(id: "SecondWindow")) + .defaultSize(CGSize(width: 400, height: 700)) + ImmersiveSpace(id: "TestImmersiveSpace") {} - .immersionStyle(selection: $immersionLevel, in: .mixed, .progressive, .full) + .immersionStyle(selection: $immersionLevel, in: .mixed, .progressive, .full) } } diff --git a/packages/rn-tester/js/RNTesterApp.ios.js b/packages/rn-tester/js/RNTesterApp.ios.js index 281ee25afd77a1..8ea87c26361438 100644 --- a/packages/rn-tester/js/RNTesterApp.ios.js +++ b/packages/rn-tester/js/RNTesterApp.ios.js @@ -24,6 +24,9 @@ AppRegistry.registerComponent('SetPropertiesExampleApp', () => AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => require('./examples/RootViewSizeFlexibilityExample/RootViewSizeFlexibilityExampleApp'), ); +AppRegistry.registerComponent('SecondWindow', () => + require('./examples/SecondWindow/SecondWindow'), +); AppRegistry.registerComponent('RNTesterApp', () => RNTesterApp); // Register suitable examples for snapshot tests diff --git a/packages/rn-tester/js/examples/SecondWindow/SecondWindow.js b/packages/rn-tester/js/examples/SecondWindow/SecondWindow.js new file mode 100644 index 00000000000000..10f71a32dae917 --- /dev/null +++ b/packages/rn-tester/js/examples/SecondWindow/SecondWindow.js @@ -0,0 +1,52 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +const {WindowManager} = require('@callstack/react-native-visionos'); +const React = require('react'); +const {Button, StyleSheet, Text, View} = require('react-native'); + +type Props = $ReadOnly<{| + title?: String, +|}>; + +const SecondWindow = ({title}: Props): React.Node => { + const [counter, setCounter] = React.useState(0); + return ( + + {title} + {counter} +