diff --git a/src/screens/cozy-app/CozyAppScreen.js b/src/screens/cozy-app/CozyAppScreen.js index 215332ab7..2a48a1f91 100644 --- a/src/screens/cozy-app/CozyAppScreen.js +++ b/src/screens/cozy-app/CozyAppScreen.js @@ -1,30 +1,16 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import React, { useCallback, useMemo } from 'react' import { StatusBar, View } from 'react-native' -import { Animation } from './CozyAppScreen.Animation' -import { styles } from './CozyAppScreen.styles' -import { CozyProxyWebView } from '../../components/webviews/CozyProxyWebView' - +import { CozyProxyWebView } from '/components/webviews/CozyProxyWebView' import { NetService } from '/libs/services/NetService' - -import { flagshipUI } from '../../libs/intents/setFlagshipUI' - import { useDimensions } from '/libs/dimensions' - -import { internalMethods } from '../../libs/intents/localMethods' - import { routes } from '/constants/routes' import { useHomeStateContext } from '/screens/home/HomeStateProvider' +import { Animation } from '/screens/cozy-app/CozyAppScreen.Animation' + +import useUIState from './useUIState' -const firstHalfUI = () => - internalMethods.setFlagshipUI({ - bottomBackground: 'white', - bottomTheme: 'dark', - bottomOverlay: 'transparent', - topBackground: 'white', - topTheme: 'dark', - topOverlay: 'transparent' - }) +import { styles } from '/screens/cozy-app/CozyAppScreen.styles' const handleError = ({ nativeEvent }) => { const { code, description } = nativeEvent @@ -33,8 +19,25 @@ const handleError = ({ nativeEvent }) => { NetService.handleOffline(routes.stack) } +/** + * CozyAppScreen component. + * Renders the main screen for a Cozy app, managing UI state and animations. + * @param {{ + * route: RouteProps, + * navigation: NavigationProp, + * }} props - The props for the component, containing the route and navigation objects. + * @returns {ReactElement} The rendered CozyAppScreen component. + */ export const CozyAppScreen = ({ route, navigation }) => { - const [UIState, setUIState] = useState({}) + const { + UIState, + isFirstHalf, + isReady, + setFirstHalf, + setReady, + shouldExitAnimation, + setShouldExitAnimation + } = useUIState(route) const { bottomBackground, bottomOverlay, @@ -42,34 +45,14 @@ export const CozyAppScreen = ({ route, navigation }) => { topTheme, topOverlay } = UIState - const [isReady, setReady] = useState(false) - const [isFirstHalf, setFirstHalf] = useState(false) - const [shouldExitAnimation, setShouldExitAnimation] = useState(false) const { setShouldWaitCozyApp } = useHomeStateContext() - useEffect(() => { - flagshipUI.on('change', state => { - setUIState({ ...UIState, ...state }) - }) - - return () => { - flagshipUI.removeAllListeners() - } - }, [UIState, route]) - - useEffect(() => { - if (isReady) return // We don't want to trigger the animation UI changes twice (in case of app unlock for instance) - - !route.params.iconParams && setReady(true) - - isFirstHalf && firstHalfUI() - }, [isFirstHalf, isReady, route.params.iconParams]) const dimensions = useDimensions() const onLoadEnd = useCallback(() => { setShouldExitAnimation(true) setShouldWaitCozyApp(false) - }, [setShouldWaitCozyApp]) + }, [setShouldExitAnimation, setShouldWaitCozyApp]) const webViewStyle = useMemo( () => ({ ...styles[isFirstHalf ? 'ready' : 'notReady'] }), diff --git a/src/screens/cozy-app/useUIState.ts b/src/screens/cozy-app/useUIState.ts new file mode 100644 index 000000000..0ccafcfd4 --- /dev/null +++ b/src/screens/cozy-app/useUIState.ts @@ -0,0 +1,86 @@ +import { RouteProp } from '@react-navigation/native' +import { useEffect, useState } from 'react' + +import { internalMethods } from '/libs/intents/localMethods' +import { flagshipUI } from '/libs/intents/setFlagshipUI' + +interface UIStateType { + bottomBackground?: string + bottomTheme?: string + bottomOverlay?: string + topBackground?: string + topTheme?: string + topOverlay?: string +} + +interface ReturnType { + UIState: UIStateType + isFirstHalf: boolean + isReady: boolean + setFirstHalf: React.Dispatch> + setReady: React.Dispatch> + shouldExitAnimation: boolean + setShouldExitAnimation: React.Dispatch> +} + +/** + * Hook for managing the UI state of the CozyAppScreen component. + * @param {RouteProps} route - The route object with the required parameters for the hook. + * @returns {{ + * UIState: UIState, + * isFirstHalf: boolean, + * isReady: boolean, + * onLoadEnd: () => void, + * }} An object containing the current UI state, isFirstHalf state, isReady state, and onLoadEnd callback. + */ +const useUIState = ( + route: RouteProp, string> +): ReturnType => { + const [UIState, setUIState] = useState({}) + const [isFirstHalf, setFirstHalf] = useState(false) + const [isReady, setReady] = useState(false) + const [shouldExitAnimation, setShouldExitAnimation] = useState(false) + + const firstHalfUI = (): void => { + internalMethods.setFlagshipUI({ + bottomBackground: 'white', + bottomTheme: 'dark', + bottomOverlay: 'transparent', + topBackground: 'white', + topTheme: 'dark', + topOverlay: 'transparent' + }) + } + + useEffect(() => { + const handleFlagshipUIChange = (state: Partial) => { + setUIState(prevState => ({ ...prevState, ...state })) + } + + flagshipUI.on('change', handleFlagshipUIChange) + + return () => { + flagshipUI.off('change', handleFlagshipUIChange) + } + }, [route]) + + useEffect(() => { + if (isReady) return + + !route.params.iconParams && setReady(true) + + isFirstHalf && firstHalfUI() + }, [isFirstHalf, isReady, route.params.iconParams]) + + return { + UIState, + isFirstHalf, + isReady, + setFirstHalf, + setReady, + shouldExitAnimation, + setShouldExitAnimation + } +} + +export default useUIState diff --git a/src/screens/cozy-app/useUIstate.spec.ts b/src/screens/cozy-app/useUIstate.spec.ts new file mode 100644 index 000000000..d3fafc8f1 --- /dev/null +++ b/src/screens/cozy-app/useUIstate.spec.ts @@ -0,0 +1,71 @@ +import { RouteProp } from '@react-navigation/native' +import { waitFor } from '@testing-library/react-native' +import { renderHook, act } from '@testing-library/react-hooks' + +import useUIState from './useUIState' + +import { flagshipUI } from '/libs/intents/setFlagshipUI' + +jest.mock('/libs/intents/localMethods') +jest.mock('/libs/intents/setFlagshipUI') +jest.mock('@react-native-cookies/cookies', () => ({ + set: jest.fn() +})) +const mockRoute = { + key: '123', + name: 'CozyApp', + params: { + iconParams: { + // Mock route.params.iconParams data here + } + } +} as RouteProp, string> + +describe('useUIState', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + it('should set isFirstHalf and isReady states based on route params', () => { + const { result } = renderHook(() => useUIState(mockRoute)) + + expect(result.current.isFirstHalf).toBe(false) + expect(result.current.isReady).toBe(false) + + act(() => { + result.current.setFirstHalf(true) + result.current.setReady(true) + }) + + expect(result.current.isFirstHalf).toBe(true) + expect(result.current.isReady).toBe(true) + }) + + it('should update UIState based on flagshipUI event', async () => { + const { result } = renderHook(() => useUIState(mockRoute)) + + expect(result.current.UIState).toEqual({}) + + act(() => { + // Trigger the 'change' event on the flagshipUI object + flagshipUI.emit('change', { + bottomBackground: 'red', + bottomTheme: 'light' + }) + }) + + await waitFor(() => { + return ( + result.current.UIState.bottomBackground === 'red' && + result.current.UIState.bottomTheme === 'light' + ) + }) + + expect(result.current.UIState).toEqual({ + bottomBackground: 'red', + bottomTheme: 'light' + }) + }) + + // Add more test cases here +})