@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
293 lines (231 loc) • 7.85 kB
text/typescript
import { useContext, useEffect, useMemo, useRef } from "react";
import { BackHandler } from "react-native";
import {
useContentTypes,
usePickFromState,
} from "@applicaster/zapp-react-native-redux/hooks";
import { HooksManager } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager";
import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
import { HOOKS_EVENTS } from "../../appUtils/HooksManager/constants";
import { getRiverFromRoute, getTargetRoute } from "../../navigationUtils";
import { useConnectionInfo } from "../connection";
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
import { useNavbarState } from "../screen";
export { useNavigation } from "./useNavigation";
export { useRoute } from "./useRoute";
export { usePathname } from "./usePathname";
export { useNavigationPluginData } from "./useNavigationPluginData";
export { useNavigationType } from "./useNavigationType";
export { useGetBottomTabBarHeight } from "./useGetBottomTabBarHeight";
export { useIsScreenActive } from "./useIsScreenActive";
export { useProfilerLogging } from "./useProfilerLogging";
export {
useUniqueRouteSuffix,
useNavbarId,
useContentId,
} from "./useUniqueRouteSuffix";
/**
* This function helps to decide wether the navbar should be presented on the screen
* based on route and / or screen Data
*
* Currently the scenarios include
* - players => false
* - search screen plugin => false
* - hooks with `showNavBar: true` or `presentFullScreen` not set to true, defaults to no navbar for hook screens
* - screens with `allow_screen_plugin_presentation` in general property => false
*
* @param {String} route current route of the screen
* @param {Object} screenData payload associated with the currently presented screen
* @returns {Boolean}
*/
export function isNavBarVisible(
route: string,
screenData: ZappRiver = {} as ZappRiver,
showNavBar: boolean,
canGoBack: boolean,
videoModalState: QuickBrickVideoModalState | null = null
) {
/* screenData is not actual navigator.data
* or navigator.screenData,
* but navigator.data.screen */
if (route.includes("playable")) {
return false;
}
if (
videoModalState?.mode === "FULLSCREEN" &&
videoModalState?.visible === true
) {
return false;
}
if (screenData?.type === "qb_search_screen" && !isTV()) {
return false;
}
if (screenData?.plugin_type === "login" && isTV()) {
return false;
}
/* Match if screen is Hook */
if (route.startsWith("/hooks/")) {
const module = screenData?.module;
if (module?.presentFullScreen) {
return false;
}
return module?.showNavBar ?? false;
}
if (screenData?.hookPlugin) {
const hookPlugin = screenData?.hookPlugin?.module;
return (
hookPlugin?.showNavBar || hookPlugin?.presentFullScreen !== true || false
);
}
if (
screenData?.general?.allow_screen_plugin_presentation ||
screenData?.general?.hide_app_nav_bar
) {
return false;
}
const showSecondaryLevel =
!isTV() ||
screenData?.navigations?.some(
(navigation) => navigation.styles?.show_secondary_level_menu
);
if (canGoBack && !showSecondaryLevel) {
return false;
}
if (typeof showNavBar === "boolean" && !showNavBar) {
return false;
}
return true;
}
export const useBackHandler = (cb: () => boolean) => {
useEffect(() => {
BackHandler.addEventListener("hardwareBackPress", cb);
return () => {
BackHandler.removeEventListener("hardwareBackPress", cb);
};
}, [cb]);
};
type Callbacks = Partial<{
handleHookPresent: ({
route,
payload,
}: {
route: string;
payload: HookPluginProps;
}) => void;
handleHookError: (props: HookPluginProps & { error?: string }) => any;
handleHookCancel: (props: Omit<HookPluginProps, "callback">) => any;
handleHookComplete: (
props: Omit<HookPluginProps, "callback"> & { route: string }
) => any;
}>;
export const useZappHooksForEntry = (
entry: ZappEntry,
callbacks?: Callbacks
) => {
const {
setState,
resetState,
setIsHooksExecutionInProgress,
setIsPresentingFullScreen,
setIsRunningInBackground,
}: HookModalContextT = useContext(ZappHookModalContext.ReactContext);
const {
appData: { layoutVersion },
rivers,
plugins,
} = usePickFromState(["appData", "rivers", "plugins"]);
const contentTypes = useContentTypes();
const isOnline = useConnectionInfo(true);
useEffect(() => {
resetState();
if (entry) {
setIsHooksExecutionInProgress(true);
if (!isOnline) {
setIsHooksExecutionInProgress(false);
// TODO: ideally move this logic to hooks manager
// @ts-ignore
return callbacks?.handleHookComplete?.({ payload: entry });
}
const targetRoute = getTargetRoute(entry, "", {
layoutVersion,
contentTypes,
});
const targetScreen = getRiverFromRoute({ route: targetRoute, rivers });
const hooksOptions = {
rivers,
plugins,
targetScreen,
};
const handleHookPresent = ({ route, payload }) => {
setIsPresentingFullScreen();
setState({
path: route,
screenData: payload,
});
callbacks?.handleHookPresent?.({ route, payload });
};
const handleBackgroundHook = (hookProps) => {
const { route, payload } = hookProps;
setState({
path: route,
screenData: payload,
});
setIsRunningInBackground();
};
const handleHookError: Callbacks["handleHookError"] = (props) => {
setIsHooksExecutionInProgress(false);
callbacks?.handleHookError?.(props);
};
const handleHookCancel: Callbacks["handleHookCancel"] = (props) => {
setIsHooksExecutionInProgress(false);
callbacks?.handleHookCancel?.(props);
};
const handleHookComplete: Callbacks["handleHookComplete"] = (props) => {
setIsHooksExecutionInProgress(false);
callbacks?.handleHookComplete?.(props);
};
const hookManager = HooksManager(hooksOptions);
hookManager.subscriber
.on(HOOKS_EVENTS.PRESENT_SCREEN_HOOK, handleHookPresent)
.on(HOOKS_EVENTS.ERROR, handleHookError)
.on(HOOKS_EVENTS.CANCEL, handleHookCancel)
.on(HOOKS_EVENTS.START_BACKGROUND_HOOK, handleBackgroundHook)
.on(HOOKS_EVENTS.COMPLETE, handleHookComplete);
hookManager.handleHooks(entry);
return () => {
hookManager.subscriber
.removeHandler(HOOKS_EVENTS.PRESENT_SCREEN_HOOK, handleHookPresent)
.removeHandler(HOOKS_EVENTS.ERROR, handleHookError)
.removeHandler(HOOKS_EVENTS.CANCEL, handleHookCancel)
.removeHandler(HOOKS_EVENTS.COMPLETE, handleHookComplete);
};
}
}, [entry.id]);
};
export const useRunIfLongPress = (
options = { timeout: LONG_KEY_PRESS_TIMEOUT }
) => {
const pressTimeout = useRef<Nullable<ReturnType<typeof setTimeout>>>(null);
const resetTimeout = () => {
clearTimeout(pressTimeout.current as ReturnType<typeof setTimeout>);
};
const startTimeout = (cb: () => void) => {
pressTimeout.current = setTimeout(cb, options.timeout);
};
const onPress = (cb: () => void) => {
startTimeout(cb);
};
const onPressOut = () => {
resetTimeout();
};
return { onPress, onPressOut };
};
/**
* @returns boolean - navbar visiblity status
*/
export const useIsNavBarVisible = (): boolean => {
const { visible } = useNavbarState();
return useMemo(() => visible, [visible]);
};