From 8c75ac1ee8d896fac78e6631c30b521a1fa2a623 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 3 Jun 2024 12:35:32 -0700 Subject: [PATCH 1/9] Add ModalContext and ModalContext.Provider --- src/components/Modal/BaseModal.tsx | 114 +++++++++++++++------------ src/components/Modal/ModalContext.ts | 15 ++++ 2 files changed, 77 insertions(+), 52 deletions(-) create mode 100644 src/components/Modal/ModalContext.ts diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 198e641840a7..1dec0bbd75af 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -17,6 +17,7 @@ import variables from '@styles/variables'; import * as Modal from '@userActions/Modal'; import CONST from '@src/CONST'; import ModalContent from './ModalContent'; +import ModalContext from './ModalContext'; import type BaseModalProps from './types'; function BaseModal( @@ -200,60 +201,69 @@ function BaseModal( paddingRight: safeAreaPaddingRight ?? 0, }; + const modalContextValue = useMemo( + () => ({ + activeModalType: isVisible ? type : undefined, + }), + [isVisible, type], + ); + return ( - // this is a workaround for modal not being visible on the new arch in some cases - // it's necessary to have a non-collapseable view as a parent of the modal to prevent - // a conflict between RN core and Reanimated shadow tree operations - // position absolute is needed to prevent the view from interfering with flex layout - - e.stopPropagation()} - onBackdropPress={handleBackdropPress} - // Note: Escape key on web/desktop will trigger onBackButtonPress callback - // eslint-disable-next-line react/jsx-props-no-multi-spaces - onBackButtonPress={Modal.closeTop} - onModalShow={handleShowModal} - propagateSwipe={propagateSwipe} - onModalHide={hideModal} - onModalWillShow={saveFocusState} - onDismiss={handleDismissModal} - onSwipeComplete={() => onClose?.()} - swipeDirection={swipeDirection} - isVisible={isVisible} - backdropColor={theme.overlay} - backdropOpacity={!shouldUseCustomBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} - backdropTransitionOutTiming={0} - hasBackdrop={fullscreen} - coverScreen={fullscreen} - style={modalStyle} - deviceHeight={windowHeight} - deviceWidth={windowWidth} - animationIn={animationIn ?? modalStyleAnimationIn} - animationOut={animationOut ?? modalStyleAnimationOut} - useNativeDriver={useNativeDriverProp && useNativeDriver} - useNativeDriverForBackdrop={useNativeDriverForBackdrop && useNativeDriver} - hideModalContentWhileAnimating={hideModalContentWhileAnimating} - animationInTiming={animationInTiming} - animationOutTiming={animationOutTiming} - statusBarTranslucent={statusBarTranslucent} - onLayout={onLayout} - avoidKeyboard={avoidKeyboard} - customBackdrop={shouldUseCustomBackdrop ? : undefined} + + - - - {children} - - - - + e.stopPropagation()} + onBackdropPress={handleBackdropPress} + // Note: Escape key on web/desktop will trigger onBackButtonPress callback + // eslint-disable-next-line react/jsx-props-no-multi-spaces + onBackButtonPress={Modal.closeTop} + onModalShow={handleShowModal} + propagateSwipe={propagateSwipe} + onModalHide={hideModal} + onModalWillShow={saveFocusState} + onDismiss={handleDismissModal} + onSwipeComplete={() => onClose?.()} + swipeDirection={swipeDirection} + isVisible={isVisible} + backdropColor={theme.overlay} + backdropOpacity={!shouldUseCustomBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} + backdropTransitionOutTiming={0} + hasBackdrop={fullscreen} + coverScreen={fullscreen} + style={modalStyle} + deviceHeight={windowHeight} + deviceWidth={windowWidth} + animationIn={animationIn ?? modalStyleAnimationIn} + animationOut={animationOut ?? modalStyleAnimationOut} + useNativeDriver={useNativeDriverProp && useNativeDriver} + useNativeDriverForBackdrop={useNativeDriverForBackdrop && useNativeDriver} + hideModalContentWhileAnimating={hideModalContentWhileAnimating} + animationInTiming={animationInTiming} + animationOutTiming={animationOutTiming} + statusBarTranslucent={statusBarTranslucent} + onLayout={onLayout} + avoidKeyboard={avoidKeyboard} + customBackdrop={shouldUseCustomBackdrop ? : undefined} + > + + + {children} + + + + + ); } diff --git a/src/components/Modal/ModalContext.ts b/src/components/Modal/ModalContext.ts new file mode 100644 index 000000000000..a5c2eb21ab1d --- /dev/null +++ b/src/components/Modal/ModalContext.ts @@ -0,0 +1,15 @@ +import {createContext} from 'react'; +import type ModalType from '@src/types/utils/ModalType'; + +type ModalContextType = { + // The type of the currently displayed modal, or undefined if there is no currently displayed modal. + // Note that React Native can only display one modal at a time. + activeModalType?: ModalType; +}; + +// This context is meant to inform modal children that they are rendering in a modal (and what type of modal they are rendering in) +// Note that this is different than ONYXKEYS.MODAL.isVisible data point in that that is a global variable for whether a modal is visible or not, +// whereas this context is provided by the BaseModal component, and thus is only available to components rendered inside a modal. +const ModalContext = createContext({}); + +export default ModalContext; From a935606c460f2e37bb9468039b3c83f2349120dd Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 3 Jun 2024 13:51:10 -0700 Subject: [PATCH 2/9] Add useRootNavigationState utility hook --- src/hooks/useRootNavigationState.ts | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/hooks/useRootNavigationState.ts diff --git a/src/hooks/useRootNavigationState.ts b/src/hooks/useRootNavigationState.ts new file mode 100644 index 000000000000..5fc267292090 --- /dev/null +++ b/src/hooks/useRootNavigationState.ts @@ -0,0 +1,30 @@ +import type {EventListenerCallback, NavigationContainerEventMap} from '@react-navigation/native'; +import type {NavigationState} from '@react-navigation/routers'; +import {useCallback, useSyncExternalStore} from 'react'; +import {navigationRef} from '@libs/Navigation/Navigation'; + +/** + * This hook is a replacement for `useNavigationState` for nested navigators. + * If `useNavigationState` is used within a nested navigator then the state that's returned is the state of the nearest parent navigator, + * not the root navigator state representing the whole app's navigation tree. + * + * Use with caution, because re-rendering any component every time the root navigation state changes can be very costly for performance. + * That's why the selector is mandatory. + */ +function useRootNavigationState(selector: (state: NavigationState) => unknown) { + const getSnapshot = useCallback(() => { + if (!navigationRef.current) { + return; + } + return selector(navigationRef.current.getRootState()); + }, [selector]); + + const subscribeToRootState = useCallback((callback: EventListenerCallback) => { + const unsubscribe = navigationRef?.current?.addListener('state', callback); + return () => unsubscribe?.(); + }, []); + + return useSyncExternalStore(subscribeToRootState, getSnapshot); +} + +export default useRootNavigationState; From 2ebe8dae504bed7f5f0da8e4ebb925c199c10700 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 3 Jun 2024 14:30:34 -0700 Subject: [PATCH 3/9] Update useResponsiveLayout to correctly account for react-navigation and react-native-modal --- src/hooks/useResponsiveLayout.ts | 75 +++++++++++--------- src/hooks/useRootNavigationState.ts | 2 +- src/libs/Navigation/Navigation.ts | 13 ++-- src/pages/signin/LoginForm/BaseLoginForm.tsx | 4 +- src/pages/signin/SignInPage.tsx | 4 +- 5 files changed, 54 insertions(+), 44 deletions(-) diff --git a/src/hooks/useResponsiveLayout.ts b/src/hooks/useResponsiveLayout.ts index bf0b77ef915c..18b29653479c 100644 --- a/src/hooks/useResponsiveLayout.ts +++ b/src/hooks/useResponsiveLayout.ts @@ -1,15 +1,14 @@ -import {useEffect, useRef, useState} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; -import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {Modal} from '@src/types/onyx'; +import {useContext} from 'react'; +import ModalContext from '@components/Modal/ModalContext'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import useRootNavigationState from './useRootNavigationState'; import useWindowDimensions from './useWindowDimensions'; type ResponsiveLayoutResult = { shouldUseNarrowLayout: boolean; isSmallScreenWidth: boolean; - isInModal: boolean; + isInNarrowPaneModal: boolean; isExtraSmallScreenHeight: boolean; isMediumScreenWidth: boolean; isLargeScreenWidth: boolean; @@ -19,39 +18,47 @@ type ResponsiveLayoutResult = { /** * Hook to determine if we are on mobile devices or in the Modal Navigator. - * Use "shouldUseNarrowLayout" for "on mobile or in RHP/LHP", "isSmallScreenWidth" for "on mobile", "isInModal" for "in RHP/LHP". + * Use "shouldUseNarrowLayout" for "on mobile or in RHP/LHP", "isSmallScreenWidth" for "on mobile", "isInNarrowPaneModal" for "in RHP/LHP". + * + * There are two kinds of modals in this app: + * 1. Modal stack navigators from react-navigation + * 2. Modal components that use react-native-modal + * + * This hook is designed to handle both. `shouldUseNarrowLayout` will return `true` if any of the following are true: + * 1. The device screen width is narrow + * 2. The consuming component is the child of a "right docked" react-native-modal component + * 3. The consuming component is a screen in a modal stack navigator and not a child of a "non-right-docked" react-native-modal component. + * + * For more details on the various modal types we've defined for this app and implemented using react-native-modal, see `ModalType`. */ export default function useResponsiveLayout(): ResponsiveLayoutResult { const {isSmallScreenWidth, isExtraSmallScreenHeight, isExtraSmallScreenWidth, isMediumScreenWidth, isLargeScreenWidth, isSmallScreen} = useWindowDimensions(); - const [willAlertModalBecomeVisible] = useOnyx(ONYXKEYS.MODAL, {selector: (value: OnyxEntry) => value?.willAlertModalBecomeVisible ?? false}); + // Note: activeModalType refers to our react-native-modal component wrapper, not react-navigation's modal stack navigators. + // This means it will only be defined if the component calling this hook is a child of a modal component. See BaseModal for the provider. + const {activeModalType} = useContext(ModalContext); - const [isInModal, setIsInModal] = useState(false); - const hasSetIsInModal = useRef(false); - const updateModalStatus = () => { - if (hasSetIsInModal.current) { - return; - } - const isDisplayedInModal = Navigation.isDisplayedInModal(); - if (isInModal !== isDisplayedInModal) { - setIsInModal(isDisplayedInModal); - } - hasSetIsInModal.current = true; - }; - - useEffect(() => { - const unsubscribe = navigationRef?.current?.addListener('state', updateModalStatus); + // This refers to the state of the root navigator, and is true if and only if the topmost navigator is the "left modal navigator" or the "right modal navigator" + const isDisplayedInModalNavigator = !!useRootNavigationState(Navigation.isModalNavigatorActive); - if (navigationRef?.current?.isReady()) { - updateModalStatus(); - } + // The component calling this hook is in a "narrow pane modal" if: + const isInNarrowPaneModal = + // it's a child of the right-docked modal + activeModalType === CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED || + // or there's a "right modal navigator" or "left modal navigator" on the top of the root navigation stack + // and the component calling this hook is not the child of another modal type, such as a confirm modal + (isDisplayedInModalNavigator && !activeModalType); - return () => { - unsubscribe?.(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const shouldUseNarrowLayout = isSmallScreenWidth || isInNarrowPaneModal; - const shouldUseNarrowLayout = willAlertModalBecomeVisible ? isSmallScreenWidth : isSmallScreenWidth || isInModal; - return {shouldUseNarrowLayout, isSmallScreenWidth, isInModal, isExtraSmallScreenHeight, isExtraSmallScreenWidth, isMediumScreenWidth, isLargeScreenWidth, isSmallScreen}; + return { + shouldUseNarrowLayout, + isSmallScreenWidth, + isInNarrowPaneModal, + isExtraSmallScreenHeight, + isExtraSmallScreenWidth, + isMediumScreenWidth, + isLargeScreenWidth, + isSmallScreen, + }; } diff --git a/src/hooks/useRootNavigationState.ts b/src/hooks/useRootNavigationState.ts index 5fc267292090..14ca8473d6bc 100644 --- a/src/hooks/useRootNavigationState.ts +++ b/src/hooks/useRootNavigationState.ts @@ -11,7 +11,7 @@ import {navigationRef} from '@libs/Navigation/Navigation'; * Use with caution, because re-rendering any component every time the root navigation state changes can be very costly for performance. * That's why the selector is mandatory. */ -function useRootNavigationState(selector: (state: NavigationState) => unknown) { +function useRootNavigationState(selector: (state: NavigationState) => T): T | undefined { const getSnapshot = useCallback(() => { if (!navigationRef.current) { return; diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 3fa3e0c5c317..80ce5e65b702 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -1,5 +1,5 @@ import {findFocusedRoute} from '@react-navigation/core'; -import type {EventArg, NavigationContainerEventMap} from '@react-navigation/native'; +import type {EventArg, NavigationContainerEventMap, NavigationState} from '@react-navigation/native'; import {CommonActions, getPathFromState, StackActions} from '@react-navigation/native'; import Log from '@libs/Log'; import * as ReportUtils from '@libs/ReportUtils'; @@ -356,9 +356,12 @@ function navigateWithSwitchPolicyID(params: SwitchPolicyIDParams) { return switchPolicyID(navigationRef.current, params); } -/** Check if the modal is being displayed */ -function isDisplayedInModal() { - const state = navigationRef?.current?.getRootState(); +/** + * Check if the modal is being displayed. + * + * @param state - MUST be the state of the root navigator for this to work. Do not use a child navigator state. + */ +function isModalNavigatorActive(state: NavigationState) { const lastRoute = state?.routes?.at(-1); const lastRouteName = lastRoute?.name; return lastRouteName === NAVIGATORS.LEFT_MODAL_NAVIGATOR || lastRouteName === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; @@ -383,7 +386,7 @@ export default { parseHybridAppUrl, navigateWithSwitchPolicyID, resetToHome, - isDisplayedInModal, + isModalNavigatorActive, closeRHPFlow, setNavigationActionToMicrotaskQueue, }; diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 4286a2603341..973eaa2d8907 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -64,7 +64,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false const firstBlurred = useRef(false); const isFocused = useIsFocused(); const isLoading = useRef(false); - const {shouldUseNarrowLayout, isInModal} = useResponsiveLayout(); + const {shouldUseNarrowLayout, isInNarrowPaneModal} = useResponsiveLayout(); /** * Validate the input value and set the error for formError @@ -167,7 +167,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false return; } let focusTimeout: NodeJS.Timeout; - if (isInModal) { + if (isInNarrowPaneModal) { focusTimeout = setTimeout(() => input.current?.focus(), CONST.ANIMATED_TRANSITION); } else { input.current.focus(); diff --git a/src/pages/signin/SignInPage.tsx b/src/pages/signin/SignInPage.tsx index 16aeb7053726..112d350087b1 100644 --- a/src/pages/signin/SignInPage.tsx +++ b/src/pages/signin/SignInPage.tsx @@ -130,7 +130,7 @@ function SignInPageInner({credentials, account, activeClients = [], preferredLoc const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate, formatPhoneNumber} = useLocalize(); - const {shouldUseNarrowLayout, isInModal} = useResponsiveLayout(); + const {shouldUseNarrowLayout, isInNarrowPaneModal} = useResponsiveLayout(); const safeAreaInsets = useSafeAreaInsets(); const signInPageLayoutRef = useRef(null); const loginFormRef = useRef(null); @@ -250,7 +250,7 @@ function SignInPageInner({credentials, account, activeClients = [], preferredLoc Date: Mon, 3 Jun 2024 15:01:31 -0700 Subject: [PATCH 4/9] Add missing ? --- src/hooks/useRootNavigationState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useRootNavigationState.ts b/src/hooks/useRootNavigationState.ts index 14ca8473d6bc..3f03e1ceb253 100644 --- a/src/hooks/useRootNavigationState.ts +++ b/src/hooks/useRootNavigationState.ts @@ -13,7 +13,7 @@ import {navigationRef} from '@libs/Navigation/Navigation'; */ function useRootNavigationState(selector: (state: NavigationState) => T): T | undefined { const getSnapshot = useCallback(() => { - if (!navigationRef.current) { + if (!navigationRef?.current) { return; } return selector(navigationRef.current.getRootState()); From 4c0584559a54f9eaf50f052fcfdf0bcb4c09d432 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 3 Jun 2024 15:27:51 -0700 Subject: [PATCH 5/9] Fix misuse of shouldUseNarrowLayout --- src/components/ConfirmModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx index 51612078a88c..23a08b4306e3 100755 --- a/src/components/ConfirmModal.tsx +++ b/src/components/ConfirmModal.tsx @@ -99,7 +99,7 @@ function ConfirmModal({ image, shouldEnableNewFocusManagement, }: ConfirmModalProps) { - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isSmallScreenWidth} = useResponsiveLayout(); const styles = useThemeStyles(); return ( @@ -109,7 +109,7 @@ function ConfirmModal({ isVisible={isVisible} shouldSetModalVisibility={shouldSetModalVisibility} onModalHide={onModalHide} - type={shouldUseNarrowLayout ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM} + type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM} innerContainerStyle={image ? styles.pt0 : {}} shouldEnableNewFocusManagement={shouldEnableNewFocusManagement} > From ff5f78f279e5fbd59f5f8136371f1fda3f9f71c4 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 3 Jun 2024 15:53:06 -0700 Subject: [PATCH 6/9] Don't use shouldUseNarrowLayout in popover menu --- src/components/Popover/index.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Popover/index.tsx b/src/components/Popover/index.tsx index 2af977caa969..3ef267292e90 100644 --- a/src/components/Popover/index.tsx +++ b/src/components/Popover/index.tsx @@ -28,7 +28,7 @@ function Popover(props: PopoverProps) { animationOut = 'fadeOut', } = props; - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isSmallScreenWidth} = useResponsiveLayout(); const withoutOverlayRef = useRef(null); const {close, popover} = React.useContext(PopoverContext); @@ -55,7 +55,7 @@ function Popover(props: PopoverProps) { onClose(); }; - if (!fullscreen && !shouldUseNarrowLayout) { + if (!fullscreen && !isSmallScreenWidth) { return createPortal( Date: Mon, 3 Jun 2024 16:38:09 -0700 Subject: [PATCH 7/9] Fix style in PopoverMenu --- src/components/PopoverMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index f3fcf270cb49..e525d07fa1d3 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -98,7 +98,7 @@ function PopoverMenu({ shouldEnableNewFocusManagement, }: PopoverMenuProps) { const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isSmallScreenWidth} = useResponsiveLayout(); const selectedItemIndex = useRef(null); const [currentMenuItems, setCurrentMenuItems] = useState(menuItems); @@ -198,7 +198,7 @@ function PopoverMenu({ shouldSetModalVisibility={shouldSetModalVisibility} shouldEnableNewFocusManagement={shouldEnableNewFocusManagement} > - + {!!headerText && {headerText}} {enteredSubMenuIndexes.length > 0 && renderBackButtonItem()} {currentMenuItems.map((item, menuIndex) => ( From 2cc0597a5abc6a3178b0b791e4bc8f9787b64fb8 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 3 Jun 2024 17:06:31 -0700 Subject: [PATCH 8/9] Use isSmallScreenWidth in BaseModal --- src/components/Modal/BaseModal.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 1dec0bbd75af..b4b0aa09e240 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -56,8 +56,7 @@ function BaseModal( const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const {windowWidth, windowHeight} = useWindowDimensions(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isSmallScreenWidth, windowWidth, windowHeight} = useWindowDimensions(); const keyboardStateContextValue = useKeyboardState(); const safeAreaInsets = useSafeAreaInsets(); @@ -164,13 +163,13 @@ function BaseModal( { windowWidth, windowHeight, - isSmallScreenWidth: shouldUseNarrowLayout, + isSmallScreenWidth, }, popoverAnchorPosition, innerContainerStyle, outerStyle, ), - [StyleUtils, type, windowWidth, windowHeight, shouldUseNarrowLayout, popoverAnchorPosition, innerContainerStyle, outerStyle], + [StyleUtils, type, windowWidth, windowHeight, isSmallScreenWidth, popoverAnchorPosition, innerContainerStyle, outerStyle], ); const { From 2742e5e5321bd843e5235b569ad8ba0f16313f00 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 3 Jun 2024 17:14:24 -0700 Subject: [PATCH 9/9] Remove unused import --- src/components/Modal/BaseModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index b4b0aa09e240..e24c5b7c9c80 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -4,7 +4,6 @@ import ReactNativeModal from 'react-native-modal'; import ColorSchemeWrapper from '@components/ColorSchemeWrapper'; import useKeyboardState from '@hooks/useKeyboardState'; import usePrevious from '@hooks/usePrevious'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme';