import type React from 'react'; import { AppRegistry, ComponentProvider } from 'react-native'; import type { DefaultOptions, NavigationOption, TabBarStyle, TabItemInfo } from './Options'; import type { BuildInLayout, Layout, Route, RouteGraph, RouteConfig } from './Route'; import LayoutCommandHandler from './handler/LayoutCommandHandler'; import DispatchCommandHandler from './handler/DispatchCommandHandler'; import ResultEventHandler, { ResultType } from './handler/ResultEventHandler'; import VisibilityEventHandler, { GlobalVisibilityEventListener, VisibilityEventListener, } from './handler/VisibilityEventHandler'; import NativeEvent from './NativeEvent'; import NativeNavigation from './NativeNavigation'; import NativeGarden from './NativeGarden'; export type { ResultType } from './handler/ResultEventHandler'; import type { DispatchParams, NavigationInterceptor } from './handler/DispatchCommandHandler'; export type { DispatchParams, NavigationInterceptor } from './handler/DispatchCommandHandler'; export const { RESULT_CANCEL, RESULT_OK, RESULT_BLOCK } = NativeNavigation.getConstants(); export type NavigationSubscription = { remove: () => void; }; export type HOC = (WrappedComponent: React.ComponentType) => React.ComponentType; export interface Navigation { startRegisterComponent(hoc?: HOC): void; endRegisterComponent(): void; registerComponent( appKey: string, getComponentFunc: ComponentProvider, routeConfig?: RouteConfig, ): void; routeConfigs(): Map; setNavigationComponentWrap(wrap: (moduleName: string) => HOC): void; addGlobalVisibilityEventListener( listener: GlobalVisibilityEventListener, ): NavigationSubscription; addVisibilityEventListener( sceneId: string, listener: VisibilityEventListener, ): NavigationSubscription; dispatch(sceneId: string, action: string, params?: DispatchParams): Promise; setInterceptor(interceptor: NavigationInterceptor): void; setResult(sceneId: string, resultCode: number, data?: T): void; result(sceneId: string, resultCode: number): Promise<[number, T]>; unmount(sceneId: string): void; setRoot(layout: BuildInLayout | Layout, sticky?: boolean): Promise; setRootLayoutUpdateListener(willSetRoot?: () => void, didSetRoot?: () => void): void; currentTab(sceneId: string): Promise; findSceneIdByModuleName(moduleName: string): Promise; isStackRoot(sceneId: string): Promise; signalFirstRenderComplete(sceneId: string): void; currentRoute(): Promise; routeGraph(): Promise; setDefaultOptions(options: DefaultOptions): void; updateOptions(sceneId: string, options: NavigationOption): void; updateTabBar(sceneId: string, options: TabBarStyle): void; setTabItem(sceneId: string, item: TabItemInfo | TabItemInfo[]): void; setMenuInteractive(sceneId: string, enabled: boolean): void; } class NavigationImpl implements Navigation { private dispatchHandler = new DispatchCommandHandler(); private resultHandler = new ResultEventHandler(); private layoutHandler = new LayoutCommandHandler(); private visibilityHandler = new VisibilityEventHandler(); constructor() { this.handleTabSwitch(); this.layoutHandler.handleRootLayoutChange(); this.visibilityHandler.handleComponentVisibility(); this.resultHandler.handleComponentResult(); } private wrap?: (moduleName: string) => HOC; private hoc?: HOC; private registerEnded = false; startRegisterComponent(hoc?: HOC) { this.hoc = hoc; this.registerEnded = false; NativeNavigation.startRegisterReactComponent(); } endRegisterComponent() { if (this.registerEnded) { console.warn("Please don't call ReactRegistry#endRegisterComponent multiple times."); return; } this.registerEnded = true; NativeNavigation.endRegisterReactComponent(); } registerComponent( appKey: string, getComponentFunc: ComponentProvider, routeConfig?: RouteConfig, ) { if (routeConfig) { this.registerRoute(appKey, routeConfig); } let WrappedComponent = getComponentFunc(); if (this.hoc) { WrappedComponent = this.hoc(WrappedComponent); } const options: object = (WrappedComponent as any).navigationItem || {}; NativeNavigation.registerReactComponent(appKey, options); let RootComponent = this.wrap!(appKey)(WrappedComponent); AppRegistry.registerComponent(appKey, () => RootComponent); } private _routeConfigs = new Map(); private registerRoute(moduleName: string, route: RouteConfig) { this._routeConfigs.set(moduleName, route); } routeConfigs() { return this._routeConfigs; } setNavigationComponentWrap(wrap: (moduleName: string) => HOC) { this.wrap = wrap; } addGlobalVisibilityEventListener( listener: GlobalVisibilityEventListener, ): NavigationSubscription { this.visibilityHandler.addGlobalVisibilityEventListener(listener); return { remove: () => this.visibilityHandler.removeGlobalVisibilityEventListener(listener), }; } addVisibilityEventListener( sceneId: string, listener: VisibilityEventListener, ): NavigationSubscription { this.visibilityHandler.addVisibilityEventListener(sceneId, listener); return { remove: () => this.visibilityHandler.removeVisibilityEventListener(sceneId, listener), }; } dispatch(sceneId: string, action: string, params: DispatchParams = {}) { return this.dispatchHandler.dispatch(sceneId, action, params); } private handleTabSwitch() { NativeEvent.onSwitchTab(({ sceneId, from, to }) => { this.dispatch(sceneId, 'switchTab', { from, to }); }); } setInterceptor(interceptor: NavigationInterceptor) { this.dispatchHandler.setInterceptor(interceptor); } setResult = ( sceneId: string, resultCode: number, data: T = null as T, ): void => { if (resultCode < RESULT_OK) { console.warn( '`resultCode` can only be `RESULT_OK`, `RESULT_CANCEL` or an integer greater than 0.', ); } NativeNavigation.setResult(sceneId, resultCode, data as object); }; result(sceneId: string, resultCode: number) { return this.resultHandler.waitResult(sceneId, resultCode); } unmount(sceneId: string) { this.resultHandler.invalidateResultEventListener(sceneId); } setRoot(layout: BuildInLayout | Layout, sticky = false) { return this.layoutHandler.setRoot(layout, sticky); } setRootLayoutUpdateListener(willSetRoot = () => {}, didSetRoot = () => {}) { this.layoutHandler.setRootLayoutUpdateListener(willSetRoot, didSetRoot); } currentTab(sceneId: string) { return new Promise(resolve => { NativeNavigation.currentTab(sceneId, (_, index) => { resolve(index); }); }); } findSceneIdByModuleName(moduleName: string) { return new Promise(resolve => { NativeNavigation.findSceneIdByModuleName(moduleName, (_, sceneId) => { resolve(sceneId); }); }); } isStackRoot(sceneId: string) { return new Promise(resolve => { NativeNavigation.isStackRoot(sceneId, (_, result) => { resolve(result); }); }); } signalFirstRenderComplete(sceneId: string) { NativeNavigation.signalFirstRenderComplete(sceneId); } currentRoute(): Promise { return new Promise(resolve => { NativeNavigation.currentRoute((_, route) => { resolve(route); }); }); } routeGraph(): Promise { return new Promise(resolve => { NativeNavigation.routeGraph((_, graph) => { resolve(graph); }); }); } setDefaultOptions(options: DefaultOptions) { NativeGarden.setStyle(options); } updateOptions(sceneId: string, options: NavigationOption) { NativeGarden.updateOptions(sceneId, options); } updateTabBar(sceneId: string, options: TabBarStyle) { NativeGarden.updateTabBar(sceneId, options); } setTabItem(sceneId: string, item: TabItemInfo | TabItemInfo[]) { if (!Array.isArray(item)) { item = [item]; } NativeGarden.setTabItem(sceneId, item); } setMenuInteractive(sceneId: string, enabled: boolean) { NativeGarden.setMenuInteractive(sceneId, enabled); } } export default new NavigationImpl() as Navigation;