From d1c57ec6028b372ac95164cddb721bcd6dbbd986 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 28 Nov 2024 16:15:22 +0100 Subject: [PATCH 01/51] feat: new way to handle insets in the app --- src/components/ScreenWrapper.tsx | 170 +++++++++--------- .../defaultEdgeSpacing/index.native.ts | 10 ++ .../defaultEdgeSpacing/index.ts | 10 ++ src/hooks/useEdgeSpacing/index.ts | 19 ++ src/hooks/useStyledSafeAreaInsets.ts | 25 ++- 5 files changed, 140 insertions(+), 94 deletions(-) create mode 100644 src/hooks/useEdgeSpacing/defaultEdgeSpacing/index.native.ts create mode 100644 src/hooks/useEdgeSpacing/defaultEdgeSpacing/index.ts create mode 100644 src/hooks/useEdgeSpacing/index.ts diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index c74ccf0470d0..d46a860d71b5 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -11,6 +11,7 @@ import useInitialDimensions from '@hooks/useInitialWindowDimensions'; import useKeyboardState from '@hooks/useKeyboardState'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; import useTackInputFocus from '@hooks/useTackInputFocus'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -25,7 +26,6 @@ import HeaderGap from './HeaderGap'; import ImportedStateIndicator from './ImportedStateIndicator'; import KeyboardAvoidingView from './KeyboardAvoidingView'; import OfflineIndicator from './OfflineIndicator'; -import SafeAreaConsumer from './SafeAreaConsumer'; import withNavigationFallback from './withNavigationFallback'; type ScreenWrapperChildrenProps = { @@ -105,7 +105,11 @@ type ScreenWrapperProps = { focusTrapSettings?: FocusTrapForScreenProps['focusTrapSettings']; }; -type ScreenWrapperStatusContextType = {didScreenTransitionEnd: boolean}; +type ScreenWrapperStatusContextType = { + didScreenTransitionEnd: boolean; + isSafeAreaTopPaddingApplied: boolean; + isSafeAreaBottomPaddingApplied: boolean; +}; const ScreenWrapperStatusContext = createContext(undefined); @@ -233,96 +237,86 @@ function ScreenWrapper( // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, []); + const {insets, paddingTop, paddingBottom, safeAreaPaddingBottomStyle} = useStyledSafeAreaInsets(); + const paddingStyle: StyleProp = {}; + + const isSafeAreaTopPaddingApplied = includePaddingTop; + if (includePaddingTop) { + paddingStyle.paddingTop = paddingTop; + } + + // We always need the safe area padding bottom if we're showing the offline indicator since it is bottom-docked. + const isSafeAreaBottomPaddingApplied = includeSafeAreaPaddingBottom || (isOffline && shouldShowOfflineIndicator); + if (isSafeAreaBottomPaddingApplied) { + paddingStyle.paddingBottom = paddingBottom; + } + const isAvoidingViewportScroll = useTackInputFocus(isFocused && shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && Browser.isMobileWebKit()); - const contextValue = useMemo(() => ({didScreenTransitionEnd}), [didScreenTransitionEnd]); + const contextValue = useMemo( + () => ({didScreenTransitionEnd, isSafeAreaTopPaddingApplied, isSafeAreaBottomPaddingApplied}), + [didScreenTransitionEnd, isSafeAreaBottomPaddingApplied, isSafeAreaTopPaddingApplied], + ); return ( - - {({ - insets = { - top: 0, - bottom: 0, - left: 0, - right: 0, - }, - paddingTop, - paddingBottom, - safeAreaPaddingBottomStyle, - }) => { - const paddingStyle: StyleProp = {}; - - if (includePaddingTop) { - paddingStyle.paddingTop = paddingTop; - } - - // We always need the safe area padding bottom if we're showing the offline indicator since it is bottom-docked. - if (includeSafeAreaPaddingBottom || (isOffline && shouldShowOfflineIndicator)) { - paddingStyle.paddingBottom = paddingBottom; - } - - return ( - - + + + + - - - - - {isDevelopment && } - - { - // If props.children is a function, call it to provide the insets to the children. - typeof children === 'function' - ? children({ - insets, - safeAreaPaddingBottomStyle, - didScreenTransitionEnd, - }) - : children - } - {isSmallScreenWidth && shouldShowOfflineIndicator && ( - <> - - {/* Since import state is tightly coupled to the offline state, it is safe to display it when showing offline indicator */} - - - )} - {!shouldUseNarrowLayout && shouldShowOfflineIndicatorInWideScreen && ( - <> - - {/* Since import state is tightly coupled to the offline state, it is safe to display it when showing offline indicator */} - - - )} - - - - - - - ); - }} - + + {isDevelopment && } + + { + // If props.children is a function, call it to provide the insets to the children. + typeof children === 'function' + ? children({ + insets, + safeAreaPaddingBottomStyle, + didScreenTransitionEnd, + }) + : children + } + {isSmallScreenWidth && shouldShowOfflineIndicator && ( + <> + + {/* Since import state is tightly coupled to the offline state, it is safe to display it when showing offline indicator */} + + + )} + {!shouldUseNarrowLayout && shouldShowOfflineIndicatorInWideScreen && ( + <> + + {/* Since import state is tightly coupled to the offline state, it is safe to display it when showing offline indicator */} + + + )} + + + + + + ); } diff --git a/src/hooks/useEdgeSpacing/defaultEdgeSpacing/index.native.ts b/src/hooks/useEdgeSpacing/defaultEdgeSpacing/index.native.ts new file mode 100644 index 000000000000..73cb894aec8c --- /dev/null +++ b/src/hooks/useEdgeSpacing/defaultEdgeSpacing/index.native.ts @@ -0,0 +1,10 @@ +import type {EdgeInsets} from 'react-native-safe-area-context'; + +const defaultEdgeSpacing: EdgeInsets = { + top: 0, + left: 20, + right: 20, + bottom: 0, +}; + +export default defaultEdgeSpacing; diff --git a/src/hooks/useEdgeSpacing/defaultEdgeSpacing/index.ts b/src/hooks/useEdgeSpacing/defaultEdgeSpacing/index.ts new file mode 100644 index 000000000000..eeb8c83852f1 --- /dev/null +++ b/src/hooks/useEdgeSpacing/defaultEdgeSpacing/index.ts @@ -0,0 +1,10 @@ +import type {EdgeInsets} from 'react-native-safe-area-context'; + +const defaultEdgeSpacing: EdgeInsets = { + top: 20, + left: 20, + right: 20, + bottom: 20, +}; + +export default defaultEdgeSpacing; diff --git a/src/hooks/useEdgeSpacing/index.ts b/src/hooks/useEdgeSpacing/index.ts new file mode 100644 index 000000000000..aa90e00f0357 --- /dev/null +++ b/src/hooks/useEdgeSpacing/index.ts @@ -0,0 +1,19 @@ +import {useMemo} from 'react'; +import type {EdgeInsets} from 'react-native-safe-area-context'; +import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; +import defaultEdgeSpacing from './defaultEdgeSpacing'; + +function useEdgeSpacing(padding: Partial = {}): EdgeInsets { + const {insets} = useStyledSafeAreaInsets(); + + const spacing = useMemo(() => ({...defaultEdgeSpacing, ...padding}), [padding]); + + return { + top: Math.max(insets.top, spacing.top), + left: Math.max(insets.left, spacing.left), + bottom: Math.max(insets.bottom, spacing.bottom), + right: Math.max(insets.right, spacing.right), + }; +} + +export default useEdgeSpacing; diff --git a/src/hooks/useStyledSafeAreaInsets.ts b/src/hooks/useStyledSafeAreaInsets.ts index dd90f833d669..cc9aa002bbe7 100644 --- a/src/hooks/useStyledSafeAreaInsets.ts +++ b/src/hooks/useStyledSafeAreaInsets.ts @@ -1,3 +1,5 @@ +import {useContext} from 'react'; +import {ScreenWrapperStatusContext} from '@components/ScreenWrapper'; import useSafeAreaInsets from './useSafeAreaInsets'; import useStyleUtils from './useStyleUtils'; @@ -20,16 +22,27 @@ import useStyleUtils from './useStyleUtils'; * // Use these values to style your component accordingly * } */ -function useStyledSafeAreaInsets(safeAreaInsetsPercentage?: number) { +function useStyledSafeAreaInsets() { const StyleUtils = useStyleUtils(); const insets = useSafeAreaInsets(); + const {paddingTop, paddingBottom} = StyleUtils.getSafeAreaPadding(insets); + + const screenWrapperStatusContext = useContext(ScreenWrapperStatusContext); + const isSafeAreaTopPaddingApplied = screenWrapperStatusContext?.isSafeAreaTopPaddingApplied ?? false; + const isSafeAreaBottomPaddingApplied = screenWrapperStatusContext?.isSafeAreaBottomPaddingApplied ?? false; + + const adaptedInsets = { + ...insets, + top: isSafeAreaTopPaddingApplied ? 0 : insets?.top, + bottom: isSafeAreaBottomPaddingApplied ? 0 : insets?.bottom, + }; + const adaptedPaddingBottom = isSafeAreaBottomPaddingApplied ? 0 : paddingBottom; - const {paddingTop, paddingBottom} = StyleUtils.getSafeAreaPadding(insets, safeAreaInsetsPercentage); return { - paddingTop, - paddingBottom, - insets, - safeAreaPaddingBottomStyle: {paddingBottom}, + paddingTop: isSafeAreaTopPaddingApplied ? 0 : paddingTop, + paddingBottom: adaptedPaddingBottom, + insets: adaptedInsets, + safeAreaPaddingBottomStyle: {adaptedPaddingBottom}, }; } From 8e76218d87dc399c06f0529e8204e282460988c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 28 Nov 2024 19:04:10 +0100 Subject: [PATCH 02/51] Update src/hooks/useEdgeSpacing/index.ts Co-authored-by: edug --- src/hooks/useEdgeSpacing/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useEdgeSpacing/index.ts b/src/hooks/useEdgeSpacing/index.ts index aa90e00f0357..0d353d226a78 100644 --- a/src/hooks/useEdgeSpacing/index.ts +++ b/src/hooks/useEdgeSpacing/index.ts @@ -3,7 +3,7 @@ import type {EdgeInsets} from 'react-native-safe-area-context'; import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; import defaultEdgeSpacing from './defaultEdgeSpacing'; -function useEdgeSpacing(padding: Partial = {}): EdgeInsets { +function useEdgeSpacing(padding: Partial = CONST.EMPTY_OBJECT): EdgeInsets { const {insets} = useStyledSafeAreaInsets(); const spacing = useMemo(() => ({...defaultEdgeSpacing, ...padding}), [padding]); From 73cee7a177e02f4305655674fe24dc7357a30b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 28 Nov 2024 19:15:07 +0100 Subject: [PATCH 03/51] add const import --- src/hooks/useEdgeSpacing/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useEdgeSpacing/index.ts b/src/hooks/useEdgeSpacing/index.ts index 0d353d226a78..1739ad0331a2 100644 --- a/src/hooks/useEdgeSpacing/index.ts +++ b/src/hooks/useEdgeSpacing/index.ts @@ -1,6 +1,7 @@ import {useMemo} from 'react'; import type {EdgeInsets} from 'react-native-safe-area-context'; import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; +import CONST from '@src/CONST'; import defaultEdgeSpacing from './defaultEdgeSpacing'; function useEdgeSpacing(padding: Partial = CONST.EMPTY_OBJECT): EdgeInsets { From f08991f3da5431b8ed7a9cb5d511db87893bb66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 28 Nov 2024 19:17:59 +0100 Subject: [PATCH 04/51] fix safeAreaPaddingBottomStyle --- src/hooks/useStyledSafeAreaInsets.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hooks/useStyledSafeAreaInsets.ts b/src/hooks/useStyledSafeAreaInsets.ts index cc9aa002bbe7..9bfd01bf6175 100644 --- a/src/hooks/useStyledSafeAreaInsets.ts +++ b/src/hooks/useStyledSafeAreaInsets.ts @@ -1,4 +1,4 @@ -import {useContext} from 'react'; +import {useContext, useMemo} from 'react'; import {ScreenWrapperStatusContext} from '@components/ScreenWrapper'; import useSafeAreaInsets from './useSafeAreaInsets'; import useStyleUtils from './useStyleUtils'; @@ -38,11 +38,13 @@ function useStyledSafeAreaInsets() { }; const adaptedPaddingBottom = isSafeAreaBottomPaddingApplied ? 0 : paddingBottom; + const safeAreaPaddingBottomStyle = useMemo(() => ({paddingBottom: adaptedPaddingBottom}), [adaptedPaddingBottom]); + return { paddingTop: isSafeAreaTopPaddingApplied ? 0 : paddingTop, paddingBottom: adaptedPaddingBottom, insets: adaptedInsets, - safeAreaPaddingBottomStyle: {adaptedPaddingBottom}, + safeAreaPaddingBottomStyle, }; } From 69c8e29b5e3f901780261d1fd7169fb951cbc569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 28 Nov 2024 23:56:13 +0100 Subject: [PATCH 05/51] fix a few screens --- src/pages/NewChatConfirmPage.tsx | 5 +---- src/pages/iou/request/step/IOURequestStepConfirmation.tsx | 1 - src/pages/iou/request/step/IOURequestStepDescription.tsx | 1 - src/pages/iou/request/step/IOURequestStepMerchant.tsx | 1 - 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx index e8f074ba680a..85e33cf0c598 100644 --- a/src/pages/NewChatConfirmPage.tsx +++ b/src/pages/NewChatConfirmPage.tsx @@ -132,10 +132,7 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP }, []); return ( - + diff --git a/src/pages/iou/request/step/IOURequestStepDescription.tsx b/src/pages/iou/request/step/IOURequestStepDescription.tsx index 7002dd2b3110..758770698d06 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.tsx +++ b/src/pages/iou/request/step/IOURequestStepDescription.tsx @@ -158,7 +158,6 @@ function IOURequestStepDescription({ shouldShowWrapper testID={IOURequestStepDescription.displayName} shouldShowNotFoundPage={shouldShowNotFoundPage} - includeSafeAreaPaddingBottom={false} > Date: Fri, 29 Nov 2024 00:00:40 +0100 Subject: [PATCH 06/51] fix workspace invite page --- src/pages/workspace/WorkspaceInvitePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 64f60f42bbed..c3a0b901a1e1 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -299,7 +299,6 @@ function WorkspaceInvitePage({route, policy}: WorkspaceInvitePageProps) { shouldEnableMaxHeight shouldUseCachedViewportHeight testID={WorkspaceInvitePage.displayName} - includeSafeAreaPaddingBottom={false} onEntryTransitionEnd={() => setDidScreenTransitionEnd(true)} > From c7ffc53671ffa8987174213865bd9b0987322f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 29 Nov 2024 12:08:45 +0100 Subject: [PATCH 07/51] wip --- src/components/FixedFooter.tsx | 11 +- .../SelectionList/BaseSelectionList.tsx | 163 +++++++++--------- src/pages/iou/request/IOURequestStartPage.tsx | 119 +++++++------ 3 files changed, 147 insertions(+), 146 deletions(-) diff --git a/src/components/FixedFooter.tsx b/src/components/FixedFooter.tsx index c9d60f3ced46..583e6aaac904 100644 --- a/src/components/FixedFooter.tsx +++ b/src/components/FixedFooter.tsx @@ -2,6 +2,7 @@ import type {ReactNode} from 'react'; import React from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; +import useEdgeSpacing from '@hooks/useEdgeSpacing'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -13,17 +14,21 @@ type FixedFooterProps = { style?: StyleProp; }; +// ScreenWrapper (Web: 0, Mobile: 24/variable) +// Component (All platforms: bottomPadding: 20) ? + function FixedFooter({style, children}: FixedFooterProps) { - const insets = useSafeAreaInsets(); + // const insets = useSafeAreaInsets(); + const {bottom} = useEdgeSpacing(); const styles = useThemeStyles(); if (!children) { return null; } - const shouldAddBottomPadding = !insets.bottom; + // const shouldAddBottomPadding = !insets.bottom; - return {children}; + return {children}; } FixedFooter.displayName = 'FixedFooter'; diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index c621a4dc1820..aef880072de5 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -22,6 +22,7 @@ import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useSingleExecution from '@hooks/useSingleExecution'; +import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; import useThemeStyles from '@hooks/useThemeStyles'; import getSectionsWithIndexOffset from '@libs/getSectionsWithIndexOffset'; import Log from '@libs/Log'; @@ -763,91 +764,89 @@ function BaseSelectionList( }, ); + const {safeAreaPaddingBottomStyle} = useStyledSafeAreaInsets(); + + // TODO: test _every_ component that uses SelectionList return ( - - {({safeAreaPaddingBottomStyle}) => ( - - {shouldShowTextInput && !shouldShowTextInputAfterHeader && renderInput()} - {/* If we are loading new options we will avoid showing any header message. This is mostly because one of the header messages says there are no options. */} - {/* This is misleading because we might be in the process of loading fresh options from the server. */} - {(!isLoadingNewOptions || headerMessage !== translate('common.noResultsFound') || (flattenedSections.allOptions.length === 0 && !showLoadingPlaceholder)) && - !!headerMessage && ( - - {headerMessage} - - )} - {!!headerContent && headerContent} - {flattenedSections.allOptions.length === 0 && (showLoadingPlaceholder || shouldShowListEmptyContent) ? ( - renderListEmptyContent() - ) : ( - <> - {!listHeaderContent && header()} - ( - <> - {renderSectionHeader(arg)} - {listHeaderContent && header()} - - )} - renderItem={renderItem} - getItemLayout={getItemLayout} - onScroll={onScroll} - onScrollBeginDrag={onScrollBeginDrag} - keyExtractor={(item, index) => item.keyForList ?? `${index}`} - extraData={focusedIndex} - // the only valid values on the new arch are "white", "black", and "default", other values will cause a crash - indicatorStyle="white" - keyboardShouldPersistTaps="always" - showsVerticalScrollIndicator={showScrollIndicator} - initialNumToRender={12} - maxToRenderPerBatch={maxToRenderPerBatch} - windowSize={windowSize} - updateCellsBatchingPeriod={updateCellsBatchingPeriod} - viewabilityConfig={{viewAreaCoveragePercentThreshold: 95}} - testID="selection-list" - onLayout={onSectionListLayout} - style={[(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0, sectionListStyle]} - ListHeaderComponent={ - shouldShowTextInput && shouldShowTextInputAfterHeader ? ( - <> - {listHeaderContent} - {renderInput()} - - ) : ( - listHeaderContent - ) - } - ListFooterComponent={listFooterContent ?? ShowMoreButtonInstance} - onEndReached={onEndReached} - onEndReachedThreshold={onEndReachedThreshold} - scrollEventThrottle={scrollEventThrottle} - contentContainerStyle={contentContainerStyle} - CellRendererComponent={shouldPreventActiveCellVirtualization ? FocusAwareCellRendererComponent : undefined} - /> - {children} - - )} - {showConfirmButton && ( - -