From b328461c32de2804f9c03eecfb77acfbfb3b3e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Wed, 9 Jul 2025 13:57:08 +0200 Subject: [PATCH] Revert "Improve native top spacing for bottom docked modals when keyboard shows" --- src/components/FeatureTrainingModal.tsx | 43 +++---------------- src/components/Modal/BaseModal.tsx | 33 +------------- src/components/withKeyboardState.tsx | 16 ++----- .../utils/generators/ModalStyleUtils.ts | 19 +------- src/styles/utils/index.ts | 22 +--------- 5 files changed, 15 insertions(+), 118 deletions(-) diff --git a/src/components/FeatureTrainingModal.tsx b/src/components/FeatureTrainingModal.tsx index fa09cde1fd1d..5fff12a244bf 100644 --- a/src/components/FeatureTrainingModal.tsx +++ b/src/components/FeatureTrainingModal.tsx @@ -1,15 +1,13 @@ import type {VideoReadyForDisplayEvent} from 'expo-av'; import type {ImageContentFit} from 'expo-image'; -import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; import {Image, InteractionManager, View} from 'react-native'; -// eslint-disable-next-line no-restricted-imports -import type {ImageResizeMode, ImageSourcePropType, LayoutChangeEvent, ScrollView as RNScrollView, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import type {ImageResizeMode, ImageSourcePropType, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; import type {MergeExclusive} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {parseFSAttributes} from '@libs/Fullstory'; @@ -211,10 +209,6 @@ function FeatureTrainingModal({ const {shouldUseNarrowLayout} = useResponsiveLayout(); const {isOffline} = useNetwork(); const hasHelpButtonBeenPressed = useRef(false); - const scrollViewRef = useRef(null); - const [containerHeight, setContainerHeight] = useState(0); - const [contentHeight, setContentHeight] = useState(0); - const insets = useSafeAreaInsets(); useEffect(() => { InteractionManager.runAfterInteractions(() => { @@ -361,18 +355,8 @@ function FeatureTrainingModal({ */ useLayoutEffect(parseFSAttributes, []); - // Scrolls modal to the bottom when keyboard appears so the action buttons are visible. - useEffect(() => { - if (contentHeight <= containerHeight || onboardingIsMediumOrLargerScreenWidth || !shouldUseScrollView) { - return; - } - scrollViewRef.current?.scrollToEnd({animated: false}); - }, [contentHeight, containerHeight, onboardingIsMediumOrLargerScreenWidth, shouldUseScrollView]); - const Wrapper = shouldUseScrollView ? ScrollView : View; - const wrapperStyles = useMemo(() => (shouldUseScrollView ? StyleUtils.getScrollableFeatureTrainingModalStyles(insets) : {}), [shouldUseScrollView, StyleUtils, insets]); - return ( setContainerHeight(e.nativeEvent.layout.height) : undefined} - onContentSizeChange={shouldUseScrollView ? (_w: number, h: number) => setContentHeight(h) : undefined} fsClass={CONST.FULL_STORY.UNMASK} testID={CONST.FULL_STORY.UNMASK} > @@ -417,20 +397,9 @@ function FeatureTrainingModal({ {!!title && !!description && ( - + {typeof title === 'string' ? {title} : title} - {shouldRenderHTMLDescription ? ( - - - - ) : ( - {description} - )} + {shouldRenderHTMLDescription ? : {description}} {secondaryDescription.length > 0 && {secondaryDescription}} {children} diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index d07de6440f5f..8fcd4d25975c 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -1,6 +1,5 @@ -import React, {forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useCallback, useContext, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; -import type {LayoutChangeEvent} from 'react-native'; import type {ModalProps as ReactNativeModalProps} from 'react-native-modal'; import ReactNativeModal from 'react-native-modal'; import type {ValueOf} from 'type-fest'; @@ -18,7 +17,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import ComposerFocusManager from '@libs/ComposerFocusManager'; -import getPlatform from '@libs/getPlatform'; import NarrowPaneContext from '@libs/Navigation/AppNavigator/Navigators/NarrowPaneContext'; import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; import Navigation from '@libs/Navigation/Navigation'; @@ -111,8 +109,6 @@ function BaseModal( const {sidePanelOffset} = useSidePanel(); const sidePanelStyle = shouldApplySidePanelOffset && !isSmallScreenWidth ? {paddingRight: sidePanelOffset.current} : undefined; const keyboardStateContextValue = useKeyboardState(); - const [modalOverlapsWithTopSafeArea, setModalOverlapsWithTopSafeArea] = useState(false); - const [modalHeight, setModalHeight] = useState(0); const insets = useSafeAreaInsets(); @@ -207,29 +203,6 @@ function BaseModal( ComposerFocusManager.setReadyToFocus(uniqueModalId); }; - // Checks if modal overlaps with topSafeArea. Used to offset tall bottom docked modals with keyboard. - useEffect(() => { - if (type !== CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED || getPlatform() === CONST.PLATFORM.WEB) { - return; - } - const {paddingTop} = StyleUtils.getPlatformSafeAreaPadding(insets); - const availableHeight = windowHeight - modalHeight - keyboardStateContextValue.keyboardActiveHeight - paddingTop; - setModalOverlapsWithTopSafeArea((keyboardStateContextValue.isKeyboardAnimatingRef.current || keyboardStateContextValue.isKeyboardActive) && Math.floor(availableHeight) <= 0); - }, [ - StyleUtils, - insets, - keyboardStateContextValue.isKeyboardActive, - keyboardStateContextValue.isKeyboardAnimatingRef, - keyboardStateContextValue.keyboardActiveHeight, - modalHeight, - type, - windowHeight, - ]); - - const onViewLayout = (e: LayoutChangeEvent) => { - setModalHeight(e.nativeEvent.layout.height); - }; - const { modalStyle, modalContainerStyle, @@ -253,9 +226,8 @@ function BaseModal( innerContainerStyle, outerStyle, shouldUseModalPaddingStyle, - modalOverlapsWithTopSafeArea, ), - [StyleUtils, type, windowWidth, windowHeight, isSmallScreenWidth, popoverAnchorPosition, innerContainerStyle, outerStyle, shouldUseModalPaddingStyle, modalOverlapsWithTopSafeArea], + [StyleUtils, type, windowWidth, windowHeight, isSmallScreenWidth, popoverAnchorPosition, innerContainerStyle, outerStyle, shouldUseModalPaddingStyle], ); const modalPaddingStyles = useMemo(() => { @@ -378,7 +350,6 @@ function BaseModal( shouldPreventScroll={shouldPreventScrollOnFocus} > diff --git a/src/components/withKeyboardState.tsx b/src/components/withKeyboardState.tsx index 0b4d7ed3f0b4..0ad567668c0c 100755 --- a/src/components/withKeyboardState.tsx +++ b/src/components/withKeyboardState.tsx @@ -1,4 +1,4 @@ -import type {ReactElement, RefObject} from 'react'; +import type {MutableRefObject, ReactElement} from 'react'; import React, {createContext, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {KeyboardEvents, useKeyboardHandler} from 'react-native-keyboard-controller'; import {runOnJS} from 'react-native-reanimated'; @@ -16,25 +16,20 @@ type KeyboardStateContextValue = { /** Height of the keyboard in pixels */ keyboardHeight: number; - /** Future or present height of the keyboard in pixels. Available together with isKeyboardActive. */ - keyboardActiveHeight: number; - /** Ref to check if the keyboard is animating */ - isKeyboardAnimatingRef: RefObject; + isKeyboardAnimatingRef: MutableRefObject; }; const KeyboardStateContext = createContext({ isKeyboardShown: false, isKeyboardActive: false, keyboardHeight: 0, - keyboardActiveHeight: 0, isKeyboardAnimatingRef: {current: false}, }); function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null { const {bottom} = useSafeAreaInsets(); const [keyboardHeight, setKeyboardHeight] = useState(0); - const [keyboardActiveHeight, setKeyboardActiveHeight] = useState(0); const isKeyboardAnimatingRef = useRef(false); const [isKeyboardActive, setIsKeyboardActive] = useState(false); @@ -47,13 +42,11 @@ function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null { setKeyboardHeight(0); setIsKeyboardActive(false); }); - const keyboardWillShowListener = KeyboardEvents.addListener('keyboardWillShow', (e) => { + const keyboardWillShowListener = KeyboardEvents.addListener('keyboardWillShow', () => { setIsKeyboardActive(true); - setKeyboardActiveHeight(e.height); }); const keyboardWillHideListener = KeyboardEvents.addListener('keyboardWillHide', () => { setIsKeyboardActive(false); - setKeyboardActiveHeight(0); }); return () => { @@ -87,12 +80,11 @@ function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null { const contextValue = useMemo( () => ({ keyboardHeight, - keyboardActiveHeight, isKeyboardShown: keyboardHeight !== 0, isKeyboardAnimatingRef, isKeyboardActive, }), - [isKeyboardActive, keyboardActiveHeight, keyboardHeight], + [isKeyboardActive, keyboardHeight], ); return {children}; } diff --git a/src/styles/utils/generators/ModalStyleUtils.ts b/src/styles/utils/generators/ModalStyleUtils.ts index 3aa8ff76db37..b6be3c8b7057 100644 --- a/src/styles/utils/generators/ModalStyleUtils.ts +++ b/src/styles/utils/generators/ModalStyleUtils.ts @@ -1,6 +1,5 @@ import type {ViewStyle} from 'react-native'; import type {ModalProps} from 'react-native-modal'; -import {isMobileSafari} from '@libs/Browser'; import type {ThemeStyles} from '@styles/index'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -43,20 +42,11 @@ type GetModalStylesStyleUtil = { innerContainerStyle?: ViewStyle, outerStyle?: ViewStyle, shouldUseModalPaddingStyle?: boolean, - modalOverlapsWithTopSafeArea?: boolean, ) => GetModalStyles; }; const createModalStyleUtils: StyleUtilGenerator = ({theme, styles}) => ({ - getModalStyles: ( - type, - windowDimensions, - popoverAnchorPosition = {}, - innerContainerStyle = {}, - outerStyle = {}, - shouldUseModalPaddingStyle = true, - modalOverlapsWithTopSafeArea = false, - ): GetModalStyles => { + getModalStyles: (type, windowDimensions, popoverAnchorPosition = {}, innerContainerStyle = {}, outerStyle = {}, shouldUseModalPaddingStyle = true): GetModalStyles => { const {windowWidth, isSmallScreenWidth} = windowDimensions; let modalStyle: GetModalStyles['modalStyle'] = { @@ -228,10 +218,6 @@ const createModalStyleUtils: StyleUtilGenerator = ({the justifyContent: 'center', overflow: 'hidden', boxShadow: theme.shadow, - // Workaround for Safari not supporting interactive-widget=resizes-content, sets max height of a container modal. - // This allows better scrolling experience after keyboard shows for modals with input, that are larger than remaining screen height. - // More info https://github.com/Expensify/App/pull/62799#issuecomment-2943136220. - ...(isMobileSafari() ? {maxHeight: `${windowDimensions.windowHeight}px`} : {}), }; if (shouldUseModalPaddingStyle) { @@ -239,8 +225,7 @@ const createModalStyleUtils: StyleUtilGenerator = ({the modalContainerStyle.paddingBottom = variables.componentBorderRadiusLarge; } - shouldAddBottomSafeAreaPadding = innerContainerStyle.paddingBottom !== 0; - shouldAddTopSafeAreaMargin = modalOverlapsWithTopSafeArea; + shouldAddBottomSafeAreaPadding = true; swipeDirection = undefined; animationIn = 'slideInUp'; animationOut = 'slideOutDown'; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 633b33a4ea84..df52b0c09416 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -4,7 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {EdgeInsets} from 'react-native-safe-area-context'; import type {ValueOf} from 'type-fest'; import type ImageSVGProps from '@components/ImageSVG/types'; -import {isMobile, isMobileChrome} from '@libs/Browser'; +import {isMobile} from '@libs/Browser'; import getPlatform from '@libs/getPlatform'; import {hashText} from '@libs/UserUtils'; // eslint-disable-next-line no-restricted-imports @@ -1234,7 +1234,6 @@ const staticStyleUtils = { getBackgroundAndBorderStyle, getBackgroundColorStyle, getBackgroundColorWithOpacityStyle, - getCombinedSpacing, getPaddingLeft, getPaddingRight, getPaddingBottom, @@ -1833,25 +1832,6 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ styleObj[key] = null; return styleObj; }, {} as Nullable) as K, - getScrollableFeatureTrainingModalStyles: ( - insets: EdgeInsets, - ): { - style?: ViewStyle; - containerStyle?: ViewStyle; - } => { - const {paddingBottom: safeAreaPaddingBottom} = getPlatformSafeAreaPadding(insets); - - const paddingBottom = getCombinedSpacing(styles.pb5.paddingBottom, safeAreaPaddingBottom, true); - // Forces scroll on modal when keyboard is open and the modal larger than remaining screen height. - return { - style: isMobileChrome() - ? { - maxHeight: '100dvh', - } - : {}, - containerStyle: {paddingBottom}, - }; - }, }); type StyleUtilsType = ReturnType;