import React, { createContext, ReactNode, useCallback, useMemo, useRef, } from "react"; import { useCurrentScreenData, useNavigation, useRoute, isNavBarVisible, } from "@applicaster/zapp-react-native-utils/reactHooks"; import { useModalNavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ModalNavigationContext"; import { useNestedNavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext"; import { create } from "zustand"; import { subscribeWithSelector } from "zustand/middleware"; import { useShallow } from "zustand/react/shallow"; import { Animated } from "react-native"; interface NavBarStoreState { visible: boolean; title: string; summary: string; setVisible: (state: boolean) => void; setTitle: (title: string) => void; setSummary: (subtitle: string) => void; height: number; scrollState: number; contentPosition: string; scrollYAnimated: Animated.Value; } interface NavBarState { visible: boolean; title: string; summary: string; setVisible: (state: boolean) => void; setTitle: (title: string) => void; setSummary: (subtitle: string) => void; } const createStateStore = () => create( subscribeWithSelector((set) => ({ data: {}, setValue: (key, value) => set((state) => ({ data: { ...state.data, [key]: value, }, })), removeValue: (key) => set((state) => { const newData = { ...state.data }; delete newData[key]; return { data: newData }; }), })) ); const createStore = () => create((set) => ({ title: "", summary: "", visible: true, height: 0, scrollState: 0, contentPosition: "", scrollYAnimated: new Animated.Value(0), setTitle(title) { set({ title }); }, setSummary(summary) { set({ summary }); }, setVisible(visible) { set({ visible }); }, })); const createScreenComponentsStore = () => create(subscribeWithSelector>((_) => ({}))); const createFeedStore = () => create(subscribeWithSelector>((_) => ({}))); type ScreenContextType = { _navBarStore: ReturnType; _stateStore: ReturnType; _feedStore: ReturnType; navBar: NavBarState; legacyFormatScreenData: LegacyNavigationScreenData | null; /** * Zustand store for component-level state within a screen. * * **Purpose:** Persists state across component mount/unmount cycles (e.g., during virtualization) * and enables state sharing between components using the same key within the same screen. * * **Lifecycle:** Tied to the screen/route — recreated on each route change. * * @example * // Used by useComponentScreenState hook: * const store = useScreenContextV2()._componentStateStore; * store.setState({ 'my-key': value }); * const value = store.getState()['my-key']; */ _componentStateStore: ReturnType; }; export const ScreenContext = createContext({ _stateStore: createStateStore(), _navBarStore: createStore(), _feedStore: createFeedStore(), navBar: { visible: true, title: "", summary: "", setVisible: (_state) => void 0, setTitle: (_title) => void 0, setSummary: (_subtitle) => void 0, }, legacyFormatScreenData: {} as LegacyNavigationScreenData, _componentStateStore: createScreenComponentsStore(), }); export function ScreenContextProvider({ children, pathname, }: { children: ReactNode; pathname: string; }) { const { data, modalData, videoModalState, canGoBack } = useNavigation(); // There are 4 route scenarios // For regular screens take date from navigator stack. // is path is model grab screenData from modal state // if path is video modal grab screenData from video state // if path is hook modal grab screenData from hook state const { screenData: routeScreenData } = useRoute(); const isModal = useModalNavigationContext(); const isNested = useNestedNavigationContext(); const currentScreenScreenData = useCurrentScreenData(); const screenNavBarStateRef = useRef>( null ); const screenStateRef = useRef>( null ); const screenFeedStoreRef = useRef>( null ); const getScreenState = useCallback(() => { if (screenStateRef.current !== null) { return screenStateRef.current; } const stateStore = createStateStore(); screenStateRef.current = stateStore; return stateStore; }, []); const getScreenNavBarState = useCallback(() => { if (screenNavBarStateRef.current !== null) { return screenNavBarStateRef.current; } const navBarState = createStore(); screenNavBarStateRef.current = navBarState; return navBarState; }, []); // Assign feed store to ref to persist it across re-renders, but recreate on pathname change const screenFeedStore = useMemo(() => createFeedStore(), [pathname]); if (screenFeedStoreRef.current !== screenFeedStore) { screenFeedStoreRef.current = screenFeedStore; } // Component state store - recreated when pathname changes (route change). // Unlike _navBarStore and _stateStore (cached via refs), this store // resets only when pathname changes to provide a clean state for the new route. const componentStateStore = useMemo( () => createScreenComponentsStore(), [pathname] ); const screenNavBarState = getScreenNavBarState()( useShallow((state) => ({ visible: state.visible, title: state.title, summary: state.summary, setTitle: state.setTitle, setSummary: state.setSummary, setVisible: state.setVisible, })) ); const isHook = pathname.includes("hook"); // screenData is replacement of useScreenContext() const screenData = useMemo(() => { if (isModal && modalData) return modalData ?? ({} as NavigationScreenData); if (isNested && data?.nested) { return data?.nested ?? ({} as ScreenData); } return data ?? ({} as ScreenData); }, [isModal, isNested, modalData, data]); const isVisible = isNavBarVisible( pathname, currentScreenScreenData, // what fallback should be if currentScreenData is not available? screenNavBarState.visible, canGoBack(isHook, pathname), videoModalState ); const navBarState = useMemo( () => ({ title: screenNavBarState.title, summary: screenNavBarState.summary, visible: isVisible, setTitle: screenNavBarState.setTitle, setSummary: screenNavBarState.setSummary, setVisible: screenNavBarState.setVisible, }), [isVisible, screenNavBarState.title, screenNavBarState.summary] ); return ( ({ _navBarStore: getScreenNavBarState(), _stateStore: getScreenState(), _feedStore: screenFeedStoreRef.current, navBar: navBarState, legacyFormatScreenData: routeScreenData, _componentStateStore: componentStateStore, }), [navBarState, screenData, routeScreenData, componentStateStore] )} > {children} ); } export function withScreenContext(Component: React.ComponentType) { return function WithScreenContextWrapper(props) { const screenContext = React.useContext(ScreenContext); return ; }; }