diff --git a/.eslintrc.js b/.eslintrc.js index 6e6ad9d984b..90104cdbbe3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -317,6 +317,7 @@ module.exports = { ['types', './src/types'], ['ui', './src/ui'], ['web', './src/web'], + ['store', './src/store'], ], extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs'], }, diff --git a/src/App.tsx b/src/App.tsx index f863ed0ddb2..1d4171a340d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,12 @@ import React, { FunctionComponent, useEffect } from 'react' import { ErrorBoundary } from 'react-error-boundary' -import 'react-native-gesture-handler' // @react-navigation -import 'react-native-get-random-values' // required for `uuid` module to work import { LogBox, Platform, StatusBar } from 'react-native' import CodePush from 'react-native-code-push' +import SplashScreen from 'react-native-lottie-splash-screen' +import { onlineManager } from 'react-query' +import 'react-native-get-random-values' // required for `uuid` module to work +import 'react-native-gesture-handler' // @react-navigation // if __DEV__ import if you want to debug // import './why-did-you-render' if (process.env.NODE_ENV === 'development') { @@ -41,8 +43,8 @@ import { BatchMessaging, BatchPush } from 'libs/react-native-batch' import { configureGoogleSignin } from 'libs/react-native-google-sso/configureGoogleSignin' import { SafeAreaProvider } from 'libs/react-native-save-area-provider' import { ReactQueryClientProvider } from 'libs/react-query/ReactQueryClientProvider' -import { SplashScreenProvider } from 'libs/splashscreen' import { ThemeProvider } from 'libs/styled' +import { clearNotification, setAppReady, setNotification, useAppStore } from 'store/useAppStore' import { theme } from 'theme' import { SnackBarProvider } from 'ui/components/snackBar/SnackBarContext' @@ -81,13 +83,13 @@ const App: FunctionComponent = function () { }, []) return ( - - - - - - - + + + + + + + @@ -96,23 +98,21 @@ const App: FunctionComponent = function () { - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -121,13 +121,13 @@ const App: FunctionComponent = function () { - - - - - - - + + + + + + + ) } @@ -139,6 +139,33 @@ const AppWithMonitoring = eventMonitoring.wrap(AppWithoutMonitoring) as React.Co }> const AppWithCodepush = __DEV__ ? AppWithMonitoring : CodePush(config)(AppWithMonitoring) +// SIDE EFFECTS +useAppStore.subscribe( + (state) => state.isNetworkAvailable, + (isNetworkAvailable) => { + if (isNetworkAvailable === true) { + onlineManager.setOnline(true) + clearNotification() + } else if (isNetworkAvailable === false) { + onlineManager.setOnline(false) + setAppReady(true) + setNotification({ + type: 'info', + message: 'Aucune connexion internet. RĂ©essaie plus tard', + }) + } + } +) + +useAppStore.subscribe( + (state) => state.isAppReady, + (isAppReady) => { + if (isAppReady) { + SplashScreen.hide() + } + } +) + /** * We have an import bug in the test file App.native.test.tsx with the new eventMonitoring wrapper : WEIRD !!! : * Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) diff --git a/src/features/auth/context/AuthContext.native.test.tsx b/src/features/auth/context/AuthContext.native.test.tsx index f46bd2c3070..478bcfec0f3 100644 --- a/src/features/auth/context/AuthContext.native.test.tsx +++ b/src/features/auth/context/AuthContext.native.test.tsx @@ -3,7 +3,7 @@ import React from 'react' import { BatchProfile } from '__mocks__/@batch.com/react-native-plugin' import * as jwt from '__mocks__/jwt-decode' -import { UserProfileResponse } from 'api/gen' +import { RefreshResponse, UserProfileResponse } from 'api/gen' import { CURRENT_DATE } from 'features/auth/fixtures/fixtures' import * as NavigationRef from 'features/navigation/navigationRef' import { beneficiaryUser, nonBeneficiaryUser } from 'fixtures/user' @@ -12,8 +12,7 @@ import { amplitude } from 'libs/amplitude' import { decodedTokenWithRemainingLifetime, tokenRemainingLifetimeInMs } from 'libs/jwt/fixtures' import { clearRefreshToken, getRefreshToken, saveRefreshToken } from 'libs/keychain/keychain' import { eventMonitoring } from 'libs/monitoring' -import { NetInfoWrapper } from 'libs/network/NetInfoWrapper' -import { useNetInfo } from 'libs/network/useNetInfo' +import { useNetInfoContext as useNetInfoContextDefault } from 'libs/network/NetInfoWrapper' import * as PackageJson from 'libs/packageJson' import { QueryKeys } from 'libs/queryKeys' import { StorageKey, storage } from 'libs/storage' @@ -24,7 +23,8 @@ import { act, renderHook } from 'tests/utils' import { useAuthContext } from './AuthContext' import { AuthWrapper } from './AuthWrapper' -const mockedUseNetInfo = useNetInfo as jest.Mock +jest.mock('libs/network/NetInfoWrapper') +const mockUseNetInfoContext = useNetInfoContextDefault as jest.Mock jest.mock('libs/amplitude/amplitude') @@ -54,17 +54,25 @@ describe('AuthContext', () => { describe('useAuthContext', () => { beforeEach(() => { mockServer.getApi('/v1/me', nonBeneficiaryUser) + mockServer.postApi('/v1/refresh_access_token', {}) + mockUseNetInfoContext.mockReturnValue({ + isConnected: true, + isInternetReachable: true, + }) }) it('should not return user when logged in but no internet connection', async () => { - mockedUseNetInfo.mockReturnValueOnce({ isConnected: false, isInternetReachable: false }) + mockUseNetInfoContext.mockReturnValueOnce({ + isConnected: false, + isInternetReachable: false, + }) await saveRefreshToken('token') const result = renderUseAuthContext() await act(async () => {}) - expect(result.current).toBeNull() + expect(result.current.user).toEqual({}) }) it('should return the user when logged in with internet connection', async () => { @@ -278,12 +286,7 @@ describe('AuthContext', () => { const renderUseAuthContext = () => { const { result } = renderHook(useAuthContext, { - wrapper: ({ children }) => - reactQueryProviderHOC( - - {children} - - ), + wrapper: ({ children }) => reactQueryProviderHOC({children}), }) return result diff --git a/src/features/location/helpers/useLocationWidgetTooltip.ts b/src/features/location/helpers/useLocationWidgetTooltip.ts index 0a0d6420e03..b54bf65da89 100644 --- a/src/features/location/helpers/useLocationWidgetTooltip.ts +++ b/src/features/location/helpers/useLocationWidgetTooltip.ts @@ -3,7 +3,6 @@ import { LayoutChangeEvent, Platform } from 'react-native' import { ScreenOrigin } from 'features/location/enums' import { useLocation } from 'libs/location' -import { useSplashScreenContext } from 'libs/splashscreen' import { storage } from 'libs/storage' const START_OFFSET = 1000 @@ -12,9 +11,6 @@ const TOOLTIP_DISPLAY_DURATION = 8000 export const useLocationWidgetTooltip = (screenOrigin: ScreenOrigin) => { const touchableRef = React.useRef() - const { isSplashScreenHidden } = useSplashScreenContext() - const isNativeSplashScreenHidden = isSplashScreenHidden || Platform.OS === 'web' - const [widgetWidth, setWidgetWidth] = React.useState() const [isTooltipVisible, setIsTooltipVisible] = React.useState(false) const hideTooltip = useCallback(() => setIsTooltipVisible(false), [setIsTooltipVisible]) @@ -52,8 +48,6 @@ export const useLocationWidgetTooltip = (screenOrigin: ScreenOrigin) => { return } - if (!isNativeSplashScreenHidden || !enableTooltip) return - const displayTooltipIfNeeded = async () => { const timesLocationTooltipHasBeenDisplayed = Number( await storage.readString('times_location_tooltip_has_been_displayed') @@ -73,7 +67,7 @@ export const useLocationWidgetTooltip = (screenOrigin: ScreenOrigin) => { clearTimeout(timeoutOff) } // eslint-disable-next-line react-hooks/exhaustive-deps -- should only be called on startup - }, [isNativeSplashScreenHidden, geolocPosition]) + }, [geolocPosition]) return { isTooltipVisible, diff --git a/src/features/navigation/NavigationContainer/NavigationContainer.tsx b/src/features/navigation/NavigationContainer/NavigationContainer.tsx index 43f708faaf9..8d0d936f9bf 100644 --- a/src/features/navigation/NavigationContainer/NavigationContainer.tsx +++ b/src/features/navigation/NavigationContainer/NavigationContainer.tsx @@ -10,8 +10,9 @@ import { DefaultTheme, useTheme } from 'styled-components/native' import { RootNavigator } from 'features/navigation/RootNavigator' import { linking } from 'features/navigation/RootNavigator/linking' -import { useSplashScreenContext } from 'libs/splashscreen' +import { useInitialScreen } from 'features/navigation/RootNavigator/useInitialScreenConfig' import { storage } from 'libs/storage' +import { setAppReady } from 'store/useAppStore' import { LoadingPage } from 'ui/components/LoadingPage' import { author } from '../../../../package.json' @@ -33,12 +34,13 @@ const DOCUMENT_TITLE_OPTIONS: DocumentTitleOptions = { } export const AppNavigationContainer = () => { - const { hideSplashScreen } = useSplashScreenContext() const theme = useTheme() - const [isNavReady, setIsNavReady] = useState(false) + const [navigationStateLoaded, setNavigationStateLoaded] = useState(false) const [initialNavigationState, setInitialNavigationState] = useState() + const initialScreen = useInitialScreen() + useEffect(() => { async function restoreNavStateOnReload() { try { @@ -50,34 +52,29 @@ export const AppNavigationContainer = () => { setInitialNavigationState(savedState) } finally { - setIsNavReady(true) + setNavigationStateLoaded(true) } } restoreNavStateOnReload() }, []) - useEffect(() => { - if (isNavReady) { - hideSplashScreen?.() - } - }, [isNavReady, hideSplashScreen]) - - if (!isNavReady) { - return + if (isNavReady && navigationStateLoaded && initialScreen) { + setAppReady(true) } - return ( + return initialScreen ? ( setIsNavReady(true)} fallback={} ref={navigationRef} documentTitle={DOCUMENT_TITLE_OPTIONS} theme={getNavThemeConfig(theme)}> - + - ) + ) : null } export default AppNavigationContainer diff --git a/src/features/navigation/RootNavigator/RootNavigator.tsx b/src/features/navigation/RootNavigator/RootNavigator.tsx index f2b35cd786e..54d7e7fa0ae 100644 --- a/src/features/navigation/RootNavigator/RootNavigator.tsx +++ b/src/features/navigation/RootNavigator/RootNavigator.tsx @@ -9,13 +9,11 @@ import { PrivacyPolicy } from 'features/cookies/pages/PrivacyPolicy' import { useCurrentRoute } from 'features/navigation/helpers/useCurrentRoute' import { AccessibleTabBar } from 'features/navigation/RootNavigator/Header/AccessibleTabBar' import { RootScreenNames } from 'features/navigation/RootNavigator/types' -import { useInitialScreen } from 'features/navigation/RootNavigator/useInitialScreenConfig' import { withWebWrapper } from 'features/navigation/RootNavigator/withWebWrapper' import { TabNavigationStateProvider } from 'features/navigation/TabBar/TabNavigationStateContext' import { VenueMapFiltersStackNavigator } from 'features/navigation/VenueMapFiltersStackNavigator/VenueMapFiltersStackNavigator' import { AccessibilityRole } from 'libs/accessibilityRole/accessibilityRole' import { env } from 'libs/environment' -import { useSplashScreenContext } from 'libs/splashscreen' import { storage } from 'libs/storage' import { IconFactoryProvider } from 'ui/components/icons/IconFactoryProvider' import { LoadingPage } from 'ui/components/LoadingPage' @@ -38,7 +36,7 @@ const CheatcodesStackNavigator = lazy(async () => { }) const RootStackNavigator = withWebWrapper( - ({ initialRouteName }: { initialRouteName: RootScreenNames }) => { + ({ initialRouteName }: { initialRouteName?: RootScreenNames }) => { const { top } = useSafeAreaInsets() return ( @@ -76,14 +74,11 @@ const RootStackNavigator = withWebWrapper( } ) -export const RootNavigator: React.ComponentType = () => { +export const RootNavigator = ({ initialScreen }: { initialScreen?: RootScreenNames }) => { const mainId = uuidv4() const tabBarId = uuidv4() const { showTabBar } = useTheme() const { isLoggedIn } = useAuthContext() - const { isSplashScreenHidden } = useSplashScreenContext() - - const initialScreen = useInitialScreen() const currentRoute = useCurrentRoute() const showHeaderQuickAccess = currentRoute && currentRoute.name === 'TabNavigator' @@ -103,10 +98,6 @@ export const RootNavigator: React.ComponentType = () => { } }, [isLoggedIn]) - if (!initialScreen) { - return - } - const mainAccessibilityRole: AccessibilityRole | undefined = determineAccessibilityRole(currentRoute) @@ -121,8 +112,9 @@ export const RootNavigator: React.ComponentType = () => { ) : null} - {/* The components below are those for which we do not want their rendering to happen while the splash is displayed. */} - {isSplashScreenHidden ? : null} + {/* The components below are those for which we do not want + their rendering to happen while the splash is displayed. */} + ) } diff --git a/src/libs/network/NetInfoWrapper.native.test.tsx b/src/libs/network/NetInfoWrapper.native.test.tsx index 0ad982e3ddf..2d6608e3f84 100644 --- a/src/libs/network/NetInfoWrapper.native.test.tsx +++ b/src/libs/network/NetInfoWrapper.native.test.tsx @@ -6,19 +6,18 @@ import { View } from 'react-native' import { analytics } from 'libs/analytics' import { NetInfoWrapper, useNetInfoContext } from 'libs/network/NetInfoWrapper' import { useNetInfo } from 'libs/network/useNetInfo' -import { useSplashScreenContext } from 'libs/splashscreen' +import { setNetworkAvailable } from 'store/useAppStore' import { render, screen } from 'tests/utils' const mockedUseNetInfo = useNetInfo as unknown as jest.Mock<{ - isConnected: boolean - isInternetReachable: boolean + isConnected: boolean | null + isInternetReachable: boolean | null type: NetInfoStateType details?: Record }> -jest.mock('libs/splashscreen') -const mockUseSplashScreenContext = useSplashScreenContext as jest.Mock -mockUseSplashScreenContext.mockReturnValue({ isSplashScreenHidden: true }) +jest.mock('store/useAppStore') +const mockSetNetworkAvailable = setNetworkAvailable as jest.Mock describe('NetInfoWrapper', () => { describe('useNetInfoContext', () => { @@ -39,23 +38,6 @@ describe('NetInfoWrapper', () => { expect(onConnectionLost).not.toHaveBeenCalled() }) - it('should call onConnectionLost', () => { - mockedUseNetInfo.mockReturnValueOnce({ - isConnected: false, - isInternetReachable: true, - type: NetInfoStateType.wifi, - }) - renderNetInfoWrapper({ - onInternetConnection, - onInternetConnectionLost, - onConnection, - onConnectionLost, - }) - - expect(onConnection).not.toHaveBeenCalled() - expect(onConnectionLost).toHaveBeenCalledTimes(1) - }) - it('should call onInternetConnection', () => { mockedUseNetInfo.mockReturnValueOnce({ isConnected: true, @@ -69,14 +51,15 @@ describe('NetInfoWrapper', () => { onConnectionLost, }) + expect(mockSetNetworkAvailable).toHaveBeenCalledWith(true) expect(onInternetConnection).toHaveBeenCalledTimes(1) expect(onInternetConnectionLost).not.toHaveBeenCalled() }) - it('should call onInternetConnectionLost', () => { + it('should log network information when wifi is used', async () => { mockedUseNetInfo.mockReturnValueOnce({ - isConnected: true, - isInternetReachable: false, + isConnected: false, + isInternetReachable: true, type: NetInfoStateType.wifi, }) renderNetInfoWrapper({ @@ -86,14 +69,14 @@ describe('NetInfoWrapper', () => { onConnectionLost, }) - expect(onInternetConnection).not.toHaveBeenCalled() - expect(onInternetConnectionLost).toHaveBeenCalledTimes(1) + expect(mockSetNetworkAvailable).toHaveBeenCalledWith(false) + expect(analytics.logConnectionInfo).toHaveBeenCalledWith({ type: 'wifi' }) }) - it('should log network information when wifi is used', async () => { + it('should update global store if network info is null', () => { mockedUseNetInfo.mockReturnValueOnce({ - isConnected: false, - isInternetReachable: true, + isConnected: null, + isInternetReachable: null, type: NetInfoStateType.wifi, }) renderNetInfoWrapper({ @@ -103,7 +86,7 @@ describe('NetInfoWrapper', () => { onConnectionLost, }) - expect(analytics.logConnectionInfo).toHaveBeenCalledWith({ type: 'wifi' }) + expect(mockSetNetworkAvailable).toHaveBeenCalledWith(null) }) it('should log network information when cellular is used', async () => { @@ -144,24 +127,7 @@ describe('NetInfoWrapper', () => { expect(analytics.logConnectionInfo).not.toHaveBeenCalled() }) - it('should display children if splashscreen is not visible and there is no network', () => { - mockedUseNetInfo.mockReturnValueOnce({ - isConnected: false, - isInternetReachable: false, - type: NetInfoStateType.unknown, - }) - renderNetInfoWrapper({ - onInternetConnection, - onInternetConnectionLost, - onConnection, - onConnectionLost, - }) - - expect(screen.getByTestId('dumbComponent')).toBeOnTheScreen() - }) - - it('should not display children if splashscreen is visible and there is no network', () => { - mockUseSplashScreenContext.mockReturnValueOnce({ isSplashScreenHidden: false }) + it('should not display children if there is no network', () => { mockedUseNetInfo.mockReturnValueOnce({ isConnected: false, isInternetReachable: false, diff --git a/src/libs/network/NetInfoWrapper.tsx b/src/libs/network/NetInfoWrapper.tsx index ace14976ef1..024e075f748 100644 --- a/src/libs/network/NetInfoWrapper.tsx +++ b/src/libs/network/NetInfoWrapper.tsx @@ -2,12 +2,11 @@ import { NetInfoState, NetInfoStateType } from '@react-native-community/netinfo' import React, { createContext, memo, useContext, useEffect } from 'react' import { Platform } from 'react-native' -import { onlineManager } from 'react-query' import { analytics } from 'libs/analytics' +import { OfflinePage } from 'libs/network/OfflinePage' import { useNetInfo } from 'libs/network/useNetInfo' -import { useSplashScreenContext } from 'libs/splashscreen' -import { SNACK_BAR_TIME_OUT, useSnackBarContext } from 'ui/components/snackBar/SnackBarContext' +import { setNetworkAvailable } from 'store/useAppStore' export const NetInfoWrapper = memo(function NetInfoWrapper({ children, @@ -15,20 +14,13 @@ export const NetInfoWrapper = memo(function NetInfoWrapper({ children: React.JSX.Element }) { const networkInfo = useNetInfo() - const { showInfoSnackBar } = useSnackBarContext() - const isConnected = !!networkInfo.isConnected && !!networkInfo.isInternetReachable - const { isSplashScreenHidden } = useSplashScreenContext() + const isConnected = networkInfo.isConnected && networkInfo.isInternetReachable useEffect(() => { - onlineManager.setOnline(isConnected) - if (isConnected === false) { - showInfoSnackBar({ - message: 'Aucune connexion internet. RĂ©essaie plus tard', - timeout: SNACK_BAR_TIME_OUT, - }) - } + setNetworkAvailable(isConnected) + // setNetworkAvailable is not necessary in dependency array // eslint-disable-next-line react-hooks/exhaustive-deps - }, [networkInfo.isConnected, networkInfo.isInternetReachable]) + }, [isConnected]) useEffect(() => { const connectionType = networkInfo.type @@ -47,7 +39,7 @@ export const NetInfoWrapper = memo(function NetInfoWrapper({ return ( - {!isSplashScreenHidden && !isConnected ? null : children} + {isConnected ? children : } ) }) diff --git a/src/libs/splashscreen/__mocks__/index.ts b/src/libs/splashscreen/__mocks__/index.ts deleted file mode 100644 index 695c7e8dfea..00000000000 --- a/src/libs/splashscreen/__mocks__/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' - -export const useSplashScreenContext = jest - .fn() - .mockReturnValue({ isSplashScreenHidden: true, hideSplashScreen: jest.fn() }) - -export const SplashScreenProvider: React.FC<{ children: React.ReactElement }> = (props) => - props.children diff --git a/src/libs/splashscreen/index.ts b/src/libs/splashscreen/index.ts deleted file mode 100644 index 99d8aeda076..00000000000 --- a/src/libs/splashscreen/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useSplashScreenContext, SplashScreenProvider } from './splashscreen' diff --git a/src/libs/splashscreen/splashscreen.native.test.tsx b/src/libs/splashscreen/splashscreen.native.test.tsx deleted file mode 100644 index 1ef1ec04619..00000000000 --- a/src/libs/splashscreen/splashscreen.native.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import mockdate from 'mockdate' - -import { SplashScreenProvider, useSplashScreenContext } from 'libs/splashscreen/splashscreen' -import { renderHook, act } from 'tests/utils' - -const MIN_SPLASHSCREEN_DURATION_IN_MS = 2000 - -const TODAY = new Date('2022-10-14') - -jest.useFakeTimers() - -describe('useSplashScreenContext()', () => { - beforeEach(() => { - mockdate.set(TODAY) - jest.clearAllTimers() - }) - - it('should hide splashscreen without waiting when it has been shown for long enough', () => { - const { result } = renderSplashScreenHook() - mockdate.set(TODAY.getTime() + MIN_SPLASHSCREEN_DURATION_IN_MS) - - act(() => { - result.current.hideSplashScreen?.() - }) - - expect(result.current.isSplashScreenHidden).toBe(true) - }) - - it('should not hide splashscreen when it has not been shown for long enough', () => { - const { result } = renderSplashScreenHook() - - act(() => { - result.current.hideSplashScreen?.() - jest.advanceTimersByTime(MIN_SPLASHSCREEN_DURATION_IN_MS - 1) - }) - - expect(result.current.isSplashScreenHidden).toBe(false) - }) - - it('should hide splashscreen when it has been shown for long enough', () => { - const { result } = renderSplashScreenHook() - - act(() => { - result.current.hideSplashScreen?.() - jest.advanceTimersByTime(MIN_SPLASHSCREEN_DURATION_IN_MS) - }) - - expect(result.current.isSplashScreenHidden).toBe(true) - }) -}) - -function renderSplashScreenHook() { - return renderHook(useSplashScreenContext, { - wrapper: SplashScreenProvider, - }) -} diff --git a/src/libs/splashscreen/splashscreen.tsx b/src/libs/splashscreen/splashscreen.tsx deleted file mode 100644 index 08e60fe053b..00000000000 --- a/src/libs/splashscreen/splashscreen.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { createContext, useCallback, useContext, memo, useState, useMemo } from 'react' -import SplashScreen from 'react-native-lottie-splash-screen' - -import { SplashScreenContextInterface } from './types' - -const MIN_SPLASHSCREEN_DURATION_IN_MS = 2000 - -const SplashScreenContext = createContext({ - isSplashScreenHidden: false, -}) - -export function useSplashScreenContext() { - return useContext(SplashScreenContext) -} - -export const SplashScreenProvider = memo(function SplashScreenProvider(props: { - children: React.ReactNode -}) { - const splashScreenBeginningTime = new Date().getTime() - const [isSplashScreenHidden, setIsSplashScreenHidden] = useState(false) - - const hideSplashscreenCallback = useCallback(() => { - SplashScreen.hide() - setIsSplashScreenHidden(true) - }, []) - - const hideSplashScreen = useCallback(() => { - const splashScreenDisplayDuration = new Date().getTime() - splashScreenBeginningTime - if (splashScreenDisplayDuration < MIN_SPLASHSCREEN_DURATION_IN_MS) { - setTimeout( - hideSplashscreenCallback, - MIN_SPLASHSCREEN_DURATION_IN_MS - splashScreenDisplayDuration - ) - } else { - hideSplashscreenCallback() - } - }, [splashScreenBeginningTime, hideSplashscreenCallback]) - - const value = useMemo( - () => ({ isSplashScreenHidden, hideSplashScreen }), - [hideSplashScreen, isSplashScreenHidden] - ) - return {props.children} -}) diff --git a/src/libs/splashscreen/splashscreen.web.test.tsx b/src/libs/splashscreen/splashscreen.web.test.tsx deleted file mode 100644 index d1dd7a4fb19..00000000000 --- a/src/libs/splashscreen/splashscreen.web.test.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { SplashScreenProvider, useSplashScreenContext } from 'libs/splashscreen/splashscreen' -import { renderHook } from 'tests/utils/web' - -describe('useSplashScreenContext()', () => { - it('should always have a hidden splashscreen', () => { - const { result } = renderHook(useSplashScreenContext, { - wrapper: SplashScreenProvider, - }) - - expect(result.current).toEqual({ - isSplashScreenHidden: true, - hideSplashScreen: undefined, - }) - }) -}) diff --git a/src/libs/splashscreen/splashscreen.web.tsx b/src/libs/splashscreen/splashscreen.web.tsx deleted file mode 100644 index 85d31f13029..00000000000 --- a/src/libs/splashscreen/splashscreen.web.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' - -import { SplashScreenContextInterface } from './types' - -/** - * There is no splash screen in web, but for compatibility with other platforms, - * we provide a function and a provider mocking the splashscreen context. - */ - -export function useSplashScreenContext(): SplashScreenContextInterface { - return { isSplashScreenHidden: true, hideSplashScreen: undefined } -} - -export const SplashScreenProvider: React.FC<{ children: React.ReactElement }> = (props) => - props.children diff --git a/src/libs/splashscreen/types.ts b/src/libs/splashscreen/types.ts deleted file mode 100644 index ccce7de888b..00000000000 --- a/src/libs/splashscreen/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface SplashScreenContextInterface { - isSplashScreenHidden: boolean - hideSplashScreen?: () => void -} diff --git a/src/store/useAppStore.ts b/src/store/useAppStore.ts new file mode 100644 index 00000000000..d016db37076 --- /dev/null +++ b/src/store/useAppStore.ts @@ -0,0 +1,30 @@ +// eslint-disable-next-line no-restricted-imports +import { create } from 'zustand' +import { subscribeWithSelector } from 'zustand/middleware' + +export type NotificationData = { + type: 'info' | 'success' | 'error' + message: string +} + +type State = { + isNetworkAvailable: boolean | null + isAppReady: boolean + notificationData?: NotificationData +} + +export const useAppStore = create()( + subscribeWithSelector(() => ({ + isNetworkAvailable: false, + isAppReady: false, + notificationData: undefined, + })) +) + +export const setNetworkAvailable = (value: boolean | null) => + useAppStore.setState({ isNetworkAvailable: value }) + +export const setAppReady = (value: boolean) => useAppStore.setState({ isAppReady: value }) +export const setNotification = (value: NotificationData) => + useAppStore.setState({ notificationData: value }) +export const clearNotification = () => useAppStore.setState({ notificationData: undefined }) diff --git a/src/ui/components/snackBar/SnackBarContext.tsx b/src/ui/components/snackBar/SnackBarContext.tsx index 02f89a28e97..d181a27f5fe 100644 --- a/src/ui/components/snackBar/SnackBarContext.tsx +++ b/src/ui/components/snackBar/SnackBarContext.tsx @@ -1,6 +1,8 @@ -import React, { createContext, memo, useContext, useRef, useState } from 'react' +import React, { createContext, memo, useContext, useEffect, useRef, useState } from 'react' import { useTheme } from 'styled-components/native' +import { NotificationData, useAppStore } from 'store/useAppStore' + import { mapSnackBarTypeToStyle } from './mapSnackBarTypeToStyle' import { SnackBar, SnackBarProps } from './SnackBar' import { SnackBarHelperSettings, SnackBarSettings, SnackBarType } from './types' @@ -29,6 +31,7 @@ export const SnackBarProvider = memo(function SnackBarProviderComponent({ children: React.ReactNode }) { const theme = useTheme() + const notificationData = useAppStore((state) => state.notificationData) const [snackBarProps, setSnackBarProps] = useState({ visible: false, message: '', @@ -66,6 +69,25 @@ export const SnackBarProvider = memo(function SnackBarProviderComponent({ }), }) + const handleShowSnackBarEvent = ({ message, type }: NotificationData) => { + const { showErrorSnackBar, showInfoSnackBar, showSuccessSnackBar } = snackBarToolsRef.current + const showMessageFnMap = { + error: showErrorSnackBar, + info: showInfoSnackBar, + success: showSuccessSnackBar, + } + + showMessageFnMap[type]?.({ message, timeout: SNACK_BAR_TIME_OUT }) + } + + useEffect(() => { + if (notificationData) { + handleShowSnackBarEvent(notificationData) + } else { + hideSnackBar() + } + }, [notificationData]) + return ( { extensions: allExtensions, alias: [ { - find: /^((api|cheatcodes|features|fixtures|libs|shared|theme|ui|web).*)/, + find: /^((api|cheatcodes|features|fixtures|libs|shared|theme|ui|web|store).*)/, replacement: '/src/$1', }, { find: 'react-native', replacement: 'react-native-web' },