Skip to content

Commit

Permalink
feat: implement window manager events (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski committed Jul 31, 2024
1 parent f27dd05 commit 914e327
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public struct RCTMainWindow: Scene {
var moduleName: String
var initialProps: RCTRootViewRepresentable.InitialPropsType
var onOpenURLCallback: ((URL) -> ())?
let windowId: String = "0"

@Environment(\.scenePhase) private var scenePhase


public init(moduleName: String, initialProps: RCTRootViewRepresentable.InitialPropsType = nil) {
self.moduleName = moduleName
Expand All @@ -31,13 +35,27 @@ public struct RCTMainWindow: Scene {
WindowGroup {
RCTRootViewRepresentable(moduleName: moduleName, initialProps: initialProps)
.modifier(WindowHandlingModifier())
.onChange(of: scenePhase, { _, newValue in
postWindowStateNotification(windowId: windowId, state: newValue)
})
.onOpenURL(perform: { url in
onOpenURLCallback?(url)
})
}
}
}

public func postWindowStateNotification(windowId: String, state: SwiftUI.ScenePhase) {
NotificationCenter.default.post(
name: NSNotification.Name(rawValue: "RCTWindowStateDidChange"),
object: nil,
userInfo: [
"windowId": windowId,
"state": "\(state)"
]
)
}

extension RCTMainWindow {
public func onOpenURL(perform action: @escaping (URL) -> ()) -> some Scene {
var scene = self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public struct RCTWindow : Scene {
var id: String
var sceneData: RCTSceneData?
var moduleName: String
@Environment(\.scenePhase) private var scenePhase


public init(id: String, moduleName: String, sceneData: RCTSceneData?) {
self.id = id
Expand All @@ -25,6 +27,9 @@ public struct RCTWindow : Scene {
Group {
if let sceneData {
RCTRootViewRepresentable(moduleName: moduleName, initialProps: sceneData.props)
.onChange(of: scenePhase) { _, newValue in
postWindowStateNotification(windowId: id, state: newValue)
}
}
}
.onAppear {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface RCTWindowManager : NSObject <RCTBridgeModule>
@interface RCTWindowManager : RCTEventEmitter

@end
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,41 @@
static NSString *const RCTOpenWindow = @"RCTOpenWindow";
static NSString *const RCTDismissWindow = @"RCTDismissWindow";
static NSString *const RCTUpdateWindow = @"RCTUpdateWindow";
static NSString *const RCTWindowStateDidChangeEvent = @"windowStateDidChange";

@interface RCTWindowManager () <NativeWindowManagerSpec>
static NSString *const RCTWindowStateDidChange = @"RCTWindowStateDidChange";

@interface RCTWindowManager () <NativeWindowManagerSpec> {
BOOL _hasAnyListeners;
}
@end

@implementation RCTWindowManager

RCT_EXPORT_MODULE(WindowManager)

- (void)initialize {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleWindowStateChanges:)
name:RCTWindowStateDidChange
object:nil];
}

- (void)invalidate {
[super invalidate];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

-(void)startObserving
{
_hasAnyListeners = YES;
}

- (void)stopObserving
{
_hasAnyListeners = NO;
}

RCT_EXPORT_METHOD(openWindow
: (NSString *)windowId userInfo
: (NSDictionary *)userInfo resolve
Expand Down Expand Up @@ -68,6 +95,17 @@ @implementation RCTWindowManager
});
}

- (void) handleWindowStateChanges:(NSNotification *)notification {

if (_hasAnyListeners) {
[self sendEventWithName:RCTWindowStateDidChangeEvent body:notification.userInfo];
}
}

- (NSArray<NSString *> *)supportedEvents {
return @[RCTWindowStateDidChangeEvent];
}

- (facebook::react::ModuleConstants<JS::NativeWindowManager::Constants::Builder>)constantsToExport {
return [self getConstants];
}
Expand All @@ -87,4 +125,13 @@ @implementation RCTWindowManager
return std::make_shared<facebook::react::NativeWindowManagerSpecJSI>(params);
}

+ (BOOL)requiresMainQueueSetup {
return YES;
}

- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}

@end
10 changes: 10 additions & 0 deletions packages/react-native/Libraries/WindowManager/WindowManager.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import {NativeEventSubscription} from '../EventEmitter/RCTNativeAppEventEmitter';

type WindowManagerEvents = 'windowStateDidChange';

type WindowState = {
windowId: string;
state: 'active' | 'inactive' | 'background';
};

export interface WindowStatic {
id: String;
open (props?: Object): Promise<void>;
update (props: Object): Promise<void>;
close (): Promise<void>;
addEventListener (type: WindowManagerEvents, handler: (info: WindowState) => void): NativeEventSubscription;
}

export interface WindowManagerStatic {
Expand Down
38 changes: 31 additions & 7 deletions packages/react-native/Libraries/WindowManager/WindowManager.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,50 @@
/**
* @format
* @flow strict
* @flow strict-local
* @jsdoc
*/

import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import Platform from '../Utilities/Platform';
import {type EventSubscription} from '../vendor/emitter/EventEmitter';
import NativeWindowManager from './NativeWindowManager';

const WindowManager = {
getWindow: function (id: string): Window {
export type WindowStateValues = 'inactive' | 'background' | 'active';

type WindowManagerEventDefinitions = {
windowStateDidChange: [{state: WindowStateValues, windowId: string}],
};

let emitter: ?NativeEventEmitter<WindowManagerEventDefinitions>;

if (NativeWindowManager != null) {
emitter = new NativeEventEmitter<WindowManagerEventDefinitions>(
Platform.OS !== 'ios' ? null : NativeWindowManager,
);
}

class WindowManager {
static getWindow = function (id: string): Window {
return new Window(id);
},
};

static addEventListener<K: $Keys<WindowManagerEventDefinitions>>(
type: K,
handler: (...$ElementType<WindowManagerEventDefinitions, K>) => void,
): ?EventSubscription {
return emitter?.addListener(type, handler);
}

// $FlowIgnore[unsafe-getters-setters]
get supportsMultipleScenes(): boolean {
static get supportsMultipleScenes(): boolean {
if (NativeWindowManager == null) {
return false;
}

const nativeConstants = NativeWindowManager.getConstants();
return nativeConstants.supportsMultipleScenes || false;
},
};
}
}

class Window {
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9086,16 +9086,17 @@ 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<void>;
close(): Promise<void>;
update(props: ?Object): Promise<void>;
"export type WindowStateValues = \\"inactive\\" | \\"background\\" | \\"active\\";
type WindowManagerEventDefinitions = {
windowStateDidChange: [{ state: WindowStateValues, windowId: string }],
};
declare class WindowManager {
static getWindow: $FlowFixMe;
static addEventListener<K: $Keys<WindowManagerEventDefinitions>>(
type: K,
handler: (...$ElementType<WindowManagerEventDefinitions, K>) => void
): ?EventSubscription;
static get supportsMultipleScenes(): boolean;
}
declare module.exports: WindowManager;
"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export interface Spec extends TurboModule {
// $FlowIgnore[unclear-type]
+updateWindow: (windowId: string, userInfo: Object) => Promise<void>;
+closeWindow: (windowId: string) => Promise<void>;

// RCTEventEmitter
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}

export default (TurboModuleRegistry.get<Spec>('WindowManager'): ?Spec);
11 changes: 11 additions & 0 deletions packages/rn-tester/js/examples/XR/XRExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ const secondWindow = WindowManager.getWindow('SecondWindow');
const OpenXRSession = () => {
const [isOpen, setIsOpen] = React.useState(false);

React.useEffect(() => {
const listener = WindowManager.addEventListener(
'windowStateDidChange',
data => {
console.log('Window state changed to:', data);
},
);
return () => {
listener?.remove();
};
}, []);
const openXRSession = async () => {
try {
if (!WindowManager.supportsMultipleScenes) {
Expand Down

0 comments on commit 914e327

Please sign in to comment.