diff --git a/.eslintrc.js b/.eslintrc.js index 6e6ad9d984b..d1c27a5af5f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -317,6 +317,7 @@ module.exports = { ['types', './src/types'], ['ui', './src/ui'], ['web', './src/web'], + ['events', './src/events'], ], extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs'], }, diff --git a/package.json b/package.json index ce378394341..17d000abeb7 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "date-fns-tz": "^2.0.0", "deprecated-react-native-prop-types": "^5.0.0", "design-system": "https://github.com/pass-culture/design-system.git#v0.0.14", + "eventemitter3": "^5.0.1", "firebase": "^9.6.11", "geojson": "^0.5.0", "globalthis": "^1.0.2", diff --git a/src/App.tsx b/src/App.tsx index f863ed0ddb2..50416fe273b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,11 @@ 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 { 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') { @@ -13,6 +14,14 @@ if (process.env.NODE_ENV === 'development') { import 'intl' import 'intl/locale-data/jsonp/en' +import { eventBus } from 'events/eventBus' +import { EventBusProvider } from 'events/EventBusProvider' +import { + NAVIGATION_EVENTS, + NETWORK_EVENTS, + SNACKBAR_EVENTS, + SPLASHSCREEN_EVENTS, +} from 'events/eventNames' import { AccessibilityFiltersWrapper } from 'features/accessibility/context/AccessibilityFiltersWrapper' import { AuthWrapper } from 'features/auth/context/AuthWrapper' import { SettingsWrapper } from 'features/auth/context/SettingsContext' @@ -81,53 +90,55 @@ const App: FunctionComponent = function () { }, []) return ( - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + ) } @@ -139,6 +150,25 @@ const AppWithMonitoring = eventMonitoring.wrap(AppWithoutMonitoring) as React.Co }> const AppWithCodepush = __DEV__ ? AppWithMonitoring : CodePush(config)(AppWithMonitoring) +// SIDE EFFECTS +eventBus.on(NETWORK_EVENTS.OFFLINE, () => { + onlineManager.setOnline(false) + eventBus.emit(SPLASHSCREEN_EVENTS.HIDE) + eventBus.emit(SNACKBAR_EVENTS.SHOW, { + type: 'info', + message: 'Aucune connexion internet. RĂ©essaie plus tard', + }) +}) + +eventBus.on(NETWORK_EVENTS.ONLINE, () => { + onlineManager.setOnline(true) + eventBus.emit(SNACKBAR_EVENTS.HIDE) +}) + +eventBus.once(NAVIGATION_EVENTS.READY, () => { + eventBus.emit(SPLASHSCREEN_EVENTS.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/events/EventBusContext.tsx b/src/events/EventBusContext.tsx new file mode 100644 index 00000000000..0a1e33c7dfe --- /dev/null +++ b/src/events/EventBusContext.tsx @@ -0,0 +1,4 @@ +import EventEmitter from 'eventemitter3' +import React from 'react' + +export const EventBusContext = React.createContext(new EventEmitter()) diff --git a/src/events/EventBusProvider.tsx b/src/events/EventBusProvider.tsx new file mode 100644 index 00000000000..24cf9632f05 --- /dev/null +++ b/src/events/EventBusProvider.tsx @@ -0,0 +1,8 @@ +import React, { PropsWithChildren } from 'react' + +import { eventBus } from './eventBus' +import { EventBusContext } from './EventBusContext' + +export const EventBusProvider = ({ children }: PropsWithChildren) => { + return {children} +} diff --git a/src/events/eventBus.ts b/src/events/eventBus.ts new file mode 100644 index 00000000000..f63d5882f3e --- /dev/null +++ b/src/events/eventBus.ts @@ -0,0 +1,3 @@ +import EventEmitter from 'eventemitter3' + +export const eventBus = new EventEmitter() diff --git a/src/events/eventNames.ts b/src/events/eventNames.ts new file mode 100644 index 00000000000..1d4ac42a0bd --- /dev/null +++ b/src/events/eventNames.ts @@ -0,0 +1,18 @@ +export const SPLASHSCREEN_EVENTS = { + HIDE: 'splashscreen.hide', + SHOW: 'splashscreen.show', +} + +export const NETWORK_EVENTS = { + ONLINE: 'network.online', + OFFLINE: 'network.offline', +} + +export const SNACKBAR_EVENTS = { + SHOW: 'snackbar.show', + HIDE: 'snackbar.hide', +} + +export const NAVIGATION_EVENTS = { + READY: 'navigation.ready', +} diff --git a/src/events/types.ts b/src/events/types.ts new file mode 100644 index 00000000000..4e9b6c3e446 --- /dev/null +++ b/src/events/types.ts @@ -0,0 +1,4 @@ +export type SnackBarEventPayload = { + type: 'info' | 'success' | 'error' + message: string +} diff --git a/src/events/useEventBus.ts b/src/events/useEventBus.ts new file mode 100644 index 00000000000..cd2862be0b2 --- /dev/null +++ b/src/events/useEventBus.ts @@ -0,0 +1,5 @@ +import { useContext } from 'react' + +import { EventBusContext } from './EventBusContext' + +export const useEventBus = () => useContext(EventBusContext) 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/navigation/NavigationContainer/NavigationContainer.tsx b/src/features/navigation/NavigationContainer/NavigationContainer.tsx index 43f708faaf9..3f56cf24652 100644 --- a/src/features/navigation/NavigationContainer/NavigationContainer.tsx +++ b/src/features/navigation/NavigationContainer/NavigationContainer.tsx @@ -8,9 +8,11 @@ import React, { useEffect, useState } from 'react' import { Platform } from 'react-native' import { DefaultTheme, useTheme } from 'styled-components/native' +import { NAVIGATION_EVENTS } from 'events/eventNames' +import { useEventBus } from 'events/useEventBus' 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 { LoadingPage } from 'ui/components/LoadingPage' @@ -33,12 +35,15 @@ const DOCUMENT_TITLE_OPTIONS: DocumentTitleOptions = { } export const AppNavigationContainer = () => { - const { hideSplashScreen } = useSplashScreenContext() const theme = useTheme() + const eventBus = useEventBus() const [isNavReady, setIsNavReady] = useState(false) + const [navigationStateLoaded, setNavigationStateLoaded] = useState(false) const [initialNavigationState, setInitialNavigationState] = useState() + const initialScreen = useInitialScreen() + useEffect(() => { async function restoreNavStateOnReload() { try { @@ -50,34 +55,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) { + eventBus.emit(NAVIGATION_EVENTS.READY) } - 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 9a84b157645..f806530a135 100644 --- a/src/features/navigation/RootNavigator/RootNavigator.tsx +++ b/src/features/navigation/RootNavigator/RootNavigator.tsx @@ -10,7 +10,6 @@ import { CheatcodesStackNavigator } from 'features/navigation/CheatcodesStackNav 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' @@ -18,7 +17,6 @@ import { AccessibilityRole } from 'libs/accessibilityRole/accessibilityRole' import { useSplashScreenContext } from 'libs/splashscreen' import { storage } from 'libs/storage' import { IconFactoryProvider } from 'ui/components/icons/IconFactoryProvider' -import { LoadingPage } from 'ui/components/LoadingPage' import { QuickAccess } from 'ui/web/link/QuickAccess' import { determineAccessibilityRole } from './determineAccessibilityRole' @@ -31,7 +29,7 @@ import { RootStack } from './Stack' const isWeb = Platform.OS === 'web' const RootStackNavigator = withWebWrapper( - ({ initialRouteName }: { initialRouteName: RootScreenNames }) => { + ({ initialRouteName }: { initialRouteName?: RootScreenNames }) => { const { top } = useSafeAreaInsets() return ( @@ -61,15 +59,13 @@ 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' const headerWithQuickAccess = showHeaderQuickAccess ? ( @@ -88,10 +84,6 @@ export const RootNavigator: React.ComponentType = () => { } }, [isLoggedIn]) - if (!initialScreen) { - return - } - const mainAccessibilityRole: AccessibilityRole | undefined = determineAccessibilityRole(currentRoute) diff --git a/src/libs/network/NetInfoWrapper.native.test.tsx b/src/libs/network/NetInfoWrapper.native.test.tsx index 0ad982e3ddf..9bae7a367ff 100644 --- a/src/libs/network/NetInfoWrapper.native.test.tsx +++ b/src/libs/network/NetInfoWrapper.native.test.tsx @@ -3,22 +3,35 @@ import { NetInfoStateType } from '@react-native-community/netinfo' import React from 'react' import { View } from 'react-native' +import { NETWORK_EVENTS } from 'events/eventNames' +import * as useEventBus from 'events/useEventBus' import { analytics } from 'libs/analytics' import { NetInfoWrapper, useNetInfoContext } from 'libs/network/NetInfoWrapper' import { useNetInfo } from 'libs/network/useNetInfo' -import { useSplashScreenContext } from 'libs/splashscreen' 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 }) +const mockEventBus = { + on: jest.fn(), + off: jest.fn(), + emit: jest.fn(), + addListener: jest.fn(), + listeners: jest.fn(), + listenerCount: jest.fn(), + once: jest.fn(), + removeListener: jest.fn(), + removeAllListeners: jest.fn(), + eventNames: jest.fn(), +} + +const useEventBusSpy = jest.spyOn(useEventBus, 'useEventBus') +useEventBusSpy.mockReturnValue(mockEventBus) describe('NetInfoWrapper', () => { describe('useNetInfoContext', () => { @@ -39,23 +52,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 +65,15 @@ describe('NetInfoWrapper', () => { onConnectionLost, }) + expect(mockEventBus.emit).toHaveBeenCalledWith(NETWORK_EVENTS.ONLINE) 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 +83,14 @@ describe('NetInfoWrapper', () => { onConnectionLost, }) - expect(onInternetConnection).not.toHaveBeenCalled() - expect(onInternetConnectionLost).toHaveBeenCalledTimes(1) + expect(mockEventBus.emit).toHaveBeenCalledWith(NETWORK_EVENTS.OFFLINE) + expect(analytics.logConnectionInfo).toHaveBeenCalledWith({ type: 'wifi' }) }) - it('should log network information when wifi is used', async () => { + it('should emit no event on event bus if network info is null', () => { mockedUseNetInfo.mockReturnValueOnce({ - isConnected: false, - isInternetReachable: true, + isConnected: null, + isInternetReachable: null, type: NetInfoStateType.wifi, }) renderNetInfoWrapper({ @@ -103,7 +100,7 @@ describe('NetInfoWrapper', () => { onConnectionLost, }) - expect(analytics.logConnectionInfo).toHaveBeenCalledWith({ type: 'wifi' }) + expect(mockEventBus.emit).not.toHaveBeenCalled() }) it('should log network information when cellular is used', async () => { @@ -144,24 +141,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..1b33ba1dd04 100644 --- a/src/libs/network/NetInfoWrapper.tsx +++ b/src/libs/network/NetInfoWrapper.tsx @@ -2,12 +2,12 @@ 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 { NETWORK_EVENTS } from 'events/eventNames' +import { useEventBus } from 'events/useEventBus' 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' export const NetInfoWrapper = memo(function NetInfoWrapper({ children, @@ -15,20 +15,17 @@ 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 eventBus = useEventBus() + 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, - }) + if (isConnected === null) { + return } + eventBus.emit(isConnected ? NETWORK_EVENTS.ONLINE : NETWORK_EVENTS.OFFLINE) + // eventBus 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 +44,7 @@ export const NetInfoWrapper = memo(function NetInfoWrapper({ return ( - {!isSplashScreenHidden && !isConnected ? null : children} + {isConnected ? children : } ) }) diff --git a/src/libs/splashscreen/splashscreen.native.test.tsx b/src/libs/splashscreen/splashscreen.native.test.tsx index 1ef1ec04619..276cf246586 100644 --- a/src/libs/splashscreen/splashscreen.native.test.tsx +++ b/src/libs/splashscreen/splashscreen.native.test.tsx @@ -1,56 +1,30 @@ -import mockdate from 'mockdate' +import React, { PropsWithChildren } from 'react' +import { eventBus } from 'events/eventBus' +import { EventBusProvider } from 'events/EventBusProvider' +import { SPLASHSCREEN_EVENTS } from 'events/eventNames' 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', () => { + it('should hide splashscreen when "splashscreen.hide" event is emitted', async () => { 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) + eventBus.emit(SPLASHSCREEN_EVENTS.HIDE) }) expect(result.current.isSplashScreenHidden).toBe(true) }) }) +const Wrapper = ({ children }: PropsWithChildren) => ( + + {children} + +) + function renderSplashScreenHook() { return renderHook(useSplashScreenContext, { - wrapper: SplashScreenProvider, + wrapper: Wrapper, }) } diff --git a/src/libs/splashscreen/splashscreen.tsx b/src/libs/splashscreen/splashscreen.tsx index 08e60fe053b..aa7cc77a454 100644 --- a/src/libs/splashscreen/splashscreen.tsx +++ b/src/libs/splashscreen/splashscreen.tsx @@ -1,9 +1,18 @@ -import React, { createContext, useCallback, useContext, memo, useState, useMemo } from 'react' +import React, { + createContext, + useContext, + memo, + useState, + useMemo, + useEffect, + useCallback, +} from 'react' import SplashScreen from 'react-native-lottie-splash-screen' -import { SplashScreenContextInterface } from './types' +import { SPLASHSCREEN_EVENTS } from 'events/eventNames' +import { useEventBus } from 'events/useEventBus' -const MIN_SPLASHSCREEN_DURATION_IN_MS = 2000 +import { SplashScreenContextInterface } from './types' const SplashScreenContext = createContext({ isSplashScreenHidden: false, @@ -16,29 +25,20 @@ export function useSplashScreenContext() { export const SplashScreenProvider = memo(function SplashScreenProvider(props: { children: React.ReactNode }) { - const splashScreenBeginningTime = new Date().getTime() const [isSplashScreenHidden, setIsSplashScreenHidden] = useState(false) + const eventBus = useEventBus() - const hideSplashscreenCallback = useCallback(() => { + const hideSplashScreen = 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] - ) + useEffect(() => { + eventBus.once(SPLASHSCREEN_EVENTS.HIDE, hideSplashScreen) + // eventBus is not necessary in dependency array + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const value = useMemo(() => ({ isSplashScreenHidden }), [isSplashScreenHidden]) return {props.children} }) diff --git a/src/libs/splashscreen/splashscreen.web.tsx b/src/libs/splashscreen/splashscreen.web.tsx index 85d31f13029..b369d570a9e 100644 --- a/src/libs/splashscreen/splashscreen.web.tsx +++ b/src/libs/splashscreen/splashscreen.web.tsx @@ -8,7 +8,7 @@ import { SplashScreenContextInterface } from './types' */ export function useSplashScreenContext(): SplashScreenContextInterface { - return { isSplashScreenHidden: true, hideSplashScreen: undefined } + return { isSplashScreenHidden: true } } export const SplashScreenProvider: React.FC<{ children: React.ReactElement }> = (props) => diff --git a/src/libs/splashscreen/types.ts b/src/libs/splashscreen/types.ts index ccce7de888b..a5cdc232d39 100644 --- a/src/libs/splashscreen/types.ts +++ b/src/libs/splashscreen/types.ts @@ -1,4 +1,3 @@ export interface SplashScreenContextInterface { isSplashScreenHidden: boolean - hideSplashScreen?: () => void } diff --git a/src/ui/components/snackBar/SnackBarContext.tsx b/src/ui/components/snackBar/SnackBarContext.tsx index 02f89a28e97..198f25a74c3 100644 --- a/src/ui/components/snackBar/SnackBarContext.tsx +++ b/src/ui/components/snackBar/SnackBarContext.tsx @@ -1,6 +1,10 @@ -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 { SNACKBAR_EVENTS } from 'events/eventNames' +import { SnackBarEventPayload } from 'events/types' +import { useEventBus } from 'events/useEventBus' + import { mapSnackBarTypeToStyle } from './mapSnackBarTypeToStyle' import { SnackBar, SnackBarProps } from './SnackBar' import { SnackBarHelperSettings, SnackBarSettings, SnackBarType } from './types' @@ -29,6 +33,7 @@ export const SnackBarProvider = memo(function SnackBarProviderComponent({ children: React.ReactNode }) { const theme = useTheme() + const eventBus = useEventBus() const [snackBarProps, setSnackBarProps] = useState({ visible: false, message: '', @@ -66,6 +71,28 @@ export const SnackBarProvider = memo(function SnackBarProviderComponent({ }), }) + const handleShowSnackBarEvent = ({ message, type }: SnackBarEventPayload) => { + const { showErrorSnackBar, showInfoSnackBar, showSuccessSnackBar } = snackBarToolsRef.current + const showMessageFnMap = { + error: showErrorSnackBar, + info: showInfoSnackBar, + success: showSuccessSnackBar, + } + + showMessageFnMap[type]?.({ message, timeout: SNACK_BAR_TIME_OUT }) + } + + useEffect(() => { + eventBus.on(SNACKBAR_EVENTS.SHOW, handleShowSnackBarEvent) + eventBus.on(SNACKBAR_EVENTS.HIDE, hideSnackBar) + return () => { + eventBus.off(SNACKBAR_EVENTS.SHOW, handleShowSnackBarEvent) + eventBus.off(SNACKBAR_EVENTS.HIDE, hideSnackBar) + } + // eventBus is not necessary in dependency array + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + return (