From 7d5c626a7700cc86961e8d885fb894030f90ced0 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sun, 4 May 2025 11:12:49 +0200 Subject: [PATCH 01/29] fix: remove mobile offline indicator in start chat page --- src/pages/NewChatPage.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index a3103885d9d1..73b6a4b21d65 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -17,6 +17,7 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -140,6 +141,7 @@ function NewChatPage() { // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to show offline indicator on small screen only // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const styles = useThemeStyles(); + const {isSmallScreenWidth} = useResponsiveLayout(); const personalData = useCurrentUserPersonalDetails(); const {top} = useSafeAreaInsets(); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); @@ -309,7 +311,7 @@ function NewChatPage() { const footerContent = useMemo( () => - !isDismissed || selectedOptions.length ? ( + (!isDismissed || selectedOptions.length > 0) && ( <> )} - ) : null, + ), [createGroup, selectedOptions.length, styles.mb5, translate, isDismissed], ); @@ -360,6 +362,7 @@ function NewChatPage() { initiallyFocusedOptionKey={firstKeyForList} shouldTextInputInterceptSwipe addBottomSafeAreaPadding + addOfflineIndicatorBottomSafeAreaPadding={isSmallScreenWidth} /> ); From c7597c009d5284a9436c4f39f406a8e2826e6ea7 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sun, 4 May 2025 12:05:10 +0200 Subject: [PATCH 02/29] fix: disable mobile offline indicator completely on Start chat page --- src/pages/NewChatPage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 73b6a4b21d65..073ea135e95e 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -141,7 +141,6 @@ function NewChatPage() { // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to show offline indicator on small screen only // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const styles = useThemeStyles(); - const {isSmallScreenWidth} = useResponsiveLayout(); const personalData = useCurrentUserPersonalDetails(); const {top} = useSafeAreaInsets(); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); @@ -362,7 +361,7 @@ function NewChatPage() { initiallyFocusedOptionKey={firstKeyForList} shouldTextInputInterceptSwipe addBottomSafeAreaPadding - addOfflineIndicatorBottomSafeAreaPadding={isSmallScreenWidth} + addOfflineIndicatorBottomSafeAreaPadding={false} /> ); From 1aa0b340ab86ece3698ea3c6c104f5a5c26ce530 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sun, 4 May 2025 12:06:06 +0200 Subject: [PATCH 03/29] fix: add useOnyx canBeMissing --- src/pages/NewChatPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 073ea135e95e..bafb4b7e0ce3 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -48,8 +48,8 @@ const excludedGroupEmails: string[] = CONST.EXPENSIFY_EMAILS.filter((value) => v function useOptions() { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedOptions, setSelectedOptions] = useState>([]); - const [betas] = useOnyx(ONYXKEYS.BETAS); - const [newGroupDraft] = useOnyx(ONYXKEYS.NEW_GROUP_CHAT_DRAFT); + const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); + const [newGroupDraft] = useOnyx(ONYXKEYS.NEW_GROUP_CHAT_DRAFT, {canBeMissing: true}); const personalData = useCurrentUserPersonalDetails(); const {didScreenTransitionEnd} = useScreenWrapperTransitionStatus(); const {options: listOptions, areOptionsInitialized} = useOptionsList({ @@ -143,7 +143,7 @@ function NewChatPage() { const styles = useThemeStyles(); const personalData = useCurrentUserPersonalDetails(); const {top} = useSafeAreaInsets(); - const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); + const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const selectionListRef = useRef(null); const {headerMessage, searchTerm, debouncedSearchTerm, setSearchTerm, selectedOptions, setSelectedOptions, recentReports, personalDetails, userToInvite, areOptionsInitialized} = From 4a3cf99b043b0bd41f5aafcdf976c0cc2306ef34 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sun, 4 May 2025 13:40:16 +0200 Subject: [PATCH 04/29] fix: eslint errors --- src/pages/NewChatPage.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index bafb4b7e0ce3..9e79de13d7af 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -17,7 +17,6 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -45,9 +44,14 @@ import KeyboardUtils from '@src/utils/keyboard'; const excludedGroupEmails: string[] = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); +type SelectedOption = ListItem & + Omit & { + reportID?: string; + }; + function useOptions() { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); - const [selectedOptions, setSelectedOptions] = useState>([]); + const [selectedOptions, setSelectedOptions] = useState([]); const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); const [newGroupDraft] = useOnyx(ONYXKEYS.NEW_GROUP_CHAT_DRAFT, {canBeMissing: true}); const personalData = useCurrentUserPersonalDetails(); @@ -153,7 +157,7 @@ function NewChatPage() { const sectionsList: Section[] = []; let firstKey = ''; - const formatResults = formatSectionsFromSearchTerm(debouncedSearchTerm, selectedOptions, recentReports, personalDetails); + const formatResults = formatSectionsFromSearchTerm(debouncedSearchTerm, selectedOptions as OptionData[], recentReports, personalDetails); sectionsList.push(formatResults.section); if (!firstKey) { @@ -199,12 +203,12 @@ function NewChatPage() { (option: ListItem & Partial) => { const isOptionInList = !!option.isSelected; - let newSelectedOptions; + let newSelectedOptions: SelectedOption[]; if (isOptionInList) { newSelectedOptions = reject(selectedOptions, (selectedOption) => selectedOption.login === option.login); } else { - newSelectedOptions = [...selectedOptions, {...option, isSelected: true, selected: true, reportID: option.reportID ?? `${CONST.DEFAULT_NUMBER_ID}`}]; + newSelectedOptions = [...selectedOptions, {...option, isSelected: true, selected: true, reportID: option.reportID}]; selectionListRef?.current?.scrollToIndex(0, true); } @@ -297,7 +301,7 @@ function NewChatPage() { if (!personalData || !personalData.login || !personalData.accountID) { return; } - const selectedParticipants: SelectedParticipant[] = selectedOptions.map((option: OptionData) => ({ + const selectedParticipants: SelectedParticipant[] = selectedOptions.map((option) => ({ login: option?.login, accountID: option.accountID ?? CONST.DEFAULT_NUMBER_ID, })); From 65d28357c493691ce4a1edfaf283a4622c38cbbf Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 14:10:15 +0200 Subject: [PATCH 05/29] fix: move ScreenWrapper into own directory --- src/components/{ScreenWrapper.tsx => ScreenWrapper/index.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{ScreenWrapper.tsx => ScreenWrapper/index.tsx} (100%) diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper/index.tsx similarity index 100% rename from src/components/ScreenWrapper.tsx rename to src/components/ScreenWrapper/index.tsx From b27e3694fe736e6bddc37cc873144ca16fb31486 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 14:13:53 +0200 Subject: [PATCH 06/29] fix: import order and paths --- src/components/ScreenWrapper/index.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/ScreenWrapper/index.tsx b/src/components/ScreenWrapper/index.tsx index 03b3489048f8..1f12641c2772 100644 --- a/src/components/ScreenWrapper/index.tsx +++ b/src/components/ScreenWrapper/index.tsx @@ -7,6 +7,18 @@ import {Keyboard, PanResponder, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import {PickerAvoidingView} from 'react-native-picker-select'; import type {EdgeInsets} from 'react-native-safe-area-context'; +import CustomDevMenu from '@components/CustomDevMenu'; +import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext'; +import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; +import type FocusTrapForScreenProps from '@components/FocusTrap/FocusTrapForScreen/FocusTrapProps'; +import HeaderGap from '@components/HeaderGap'; +import ImportedStateIndicator from '@components/ImportedStateIndicator'; +import {InitialURLContext} from '@components/InitialURLContextProvider'; +import {useInputBlurContext} from '@components/InputBlurContext'; +import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; +import ModalContext from '@components/Modal/ModalContext'; +import OfflineIndicator from '@components/OfflineIndicator'; +import withNavigationFallback from '@components/withNavigationFallback'; import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import useEnvironment from '@hooks/useEnvironment'; import useInitialDimensions from '@hooks/useInitialWindowDimensions'; @@ -26,18 +38,6 @@ import toggleTestToolsModal from '@userActions/TestTool'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import CustomDevMenu from './CustomDevMenu'; -import CustomStatusBarAndBackgroundContext from './CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext'; -import FocusTrapForScreens from './FocusTrap/FocusTrapForScreen'; -import type FocusTrapForScreenProps from './FocusTrap/FocusTrapForScreen/FocusTrapProps'; -import HeaderGap from './HeaderGap'; -import ImportedStateIndicator from './ImportedStateIndicator'; -import {InitialURLContext} from './InitialURLContextProvider'; -import {useInputBlurContext} from './InputBlurContext'; -import KeyboardAvoidingView from './KeyboardAvoidingView'; -import ModalContext from './Modal/ModalContext'; -import OfflineIndicator from './OfflineIndicator'; -import withNavigationFallback from './withNavigationFallback'; type ScreenWrapperChildrenProps = { insets: EdgeInsets; From 90d50032a67eedd4cfdb0c02f260e1c8dc39f81d Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 14:14:24 +0200 Subject: [PATCH 07/29] fix: encapsulate logic in useScreenWrapperTransitionStatus --- src/hooks/useScreenWrapperTransitionStatus.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useScreenWrapperTransitionStatus.ts b/src/hooks/useScreenWrapperTransitionStatus.ts index 0d58bf3120c8..de94c9708797 100644 --- a/src/hooks/useScreenWrapperTransitionStatus.ts +++ b/src/hooks/useScreenWrapperTransitionStatus.ts @@ -7,11 +7,11 @@ import {ScreenWrapperStatusContext} from '@components/ScreenWrapper'; * @returns `didScreenTransitionEnd` flag to indicate if navigation transition ended. */ export default function useScreenWrapperTransitionStatus() { - const value = useContext(ScreenWrapperStatusContext); + const context = useContext(ScreenWrapperStatusContext); - if (value === undefined) { + if (context === undefined) { throw new Error("Couldn't find values for screen ScreenWrapper transition status. Are you inside a screen in ScreenWrapper?"); } - return value; + return {didScreenTransitionEnd: context.didScreenTransitionEnd}; } From c7c980497bd3cc2c286ae09754dd80a97736e99f Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 15:02:17 +0200 Subject: [PATCH 08/29] feat: introduce offline indicator context to globally disable offline indicator --- .../ScreenWrapperOfflineIndicatorContext.ts | 16 ++ src/components/ScreenWrapper/index.tsx | 140 +++++++++++------- .../useBottomSafeSafeAreaPaddingStyle.ts | 27 +++- 3 files changed, 122 insertions(+), 61 deletions(-) create mode 100644 src/components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext.ts diff --git a/src/components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext.ts b/src/components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext.ts new file mode 100644 index 000000000000..a1a40c7d2762 --- /dev/null +++ b/src/components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext.ts @@ -0,0 +1,16 @@ +import {createContext} from 'react'; + +type ScreenWrapperOfflineIndicatorContextType = { + showOnSmallScreens?: boolean; + showOnWideScreens?: boolean; + addSafeAreaPadding?: boolean; +}; + +const ScreenWrapperOfflineIndicatorContext = createContext({ + showOnSmallScreens: true, + showOnWideScreens: false, + addSafeAreaPadding: true, +}); + +export default ScreenWrapperOfflineIndicatorContext; +export type {ScreenWrapperOfflineIndicatorContextType}; diff --git a/src/components/ScreenWrapper/index.tsx b/src/components/ScreenWrapper/index.tsx index 1f12641c2772..7f656f6dee3d 100644 --- a/src/components/ScreenWrapper/index.tsx +++ b/src/components/ScreenWrapper/index.tsx @@ -38,6 +38,7 @@ import toggleTestToolsModal from '@userActions/TestTool'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ScreenWrapperOfflineIndicatorContext from './ScreenWrapperOfflineIndicatorContext'; type ScreenWrapperChildrenProps = { insets: EdgeInsets; @@ -98,9 +99,16 @@ type ScreenWrapperProps = { /** Whether to use the minHeight. Use true for screens where the window height are changing because of Virtual Keyboard */ shouldEnableMinHeight?: boolean; + /** Whether to disable the safe area padding for the offline indicator */ + disableOfflineIndicatorSafeAreaPadding?: boolean; + /** Whether to show offline indicator */ + shouldShowOfflineIndicator?: boolean; + /** Whether to show offline indicator on wide screens */ + shouldShowOfflineIndicatorInWideScreen?: boolean; + /** Whether to avoid scroll on virtual viewport */ shouldAvoidScrollOnVirtualViewport?: boolean; @@ -115,9 +123,6 @@ type ScreenWrapperProps = { */ navigation?: PlatformStackNavigationProp | PlatformStackNavigationProp; - /** Whether to show offline indicator on wide screens */ - shouldShowOfflineIndicatorInWideScreen?: boolean; - /** Overrides the focus trap default settings */ focusTrapSettings?: FocusTrapForScreenProps['focusTrapSettings']; @@ -161,7 +166,9 @@ function ScreenWrapper( shouldEnablePickerAvoiding = true, headerGapStyles, children, - shouldShowOfflineIndicator = true, + disableOfflineIndicatorSafeAreaPadding: disableOfflineIndicatorSafeAreaPaddingProp, + shouldShowOfflineIndicatorInWideScreen: shouldShowOfflineIndicatorInWideScreenProp, + shouldShowOfflineIndicator: shouldShowOfflineIndicatorProp, offlineIndicatorStyle, style, shouldDismissKeyboardBeforeClose = true, @@ -169,7 +176,6 @@ function ScreenWrapper( testID, navigation: navigationProp, shouldAvoidScrollOnVirtualViewport = true, - shouldShowOfflineIndicatorInWideScreen = false, shouldUseCachedViewportHeight = false, focusTrapSettings, bottomContent, @@ -360,7 +366,7 @@ function ScreenWrapper( * By default, the background color of the mobile offline indicator is opaque. * If `isOfflineIndicatorTranslucent` is set to true, a translucent background color is applied. */ - const mobileOfflineIndicatorBackgroundStyle = useMemo(() => { + const smallScreenOfflineIndicatorBackgroundStyle = useMemo(() => { const showOfflineIndicatorBackground = !bottomContent && isOffline; if (!showOfflineIndicatorBackground) { return undefined; @@ -369,7 +375,7 @@ function ScreenWrapper( }, [bottomContent, isOffline, isOfflineIndicatorTranslucent, styles.appBG, styles.translucentNavigationBarBG]); /** In edge-to-edge mode, we always want to apply the bottom safe area padding to the mobile offline indicator. */ - const hasMobileOfflineIndicatorBottomSafeAreaPadding = isUsingEdgeToEdgeMode ? enableEdgeToEdgeBottomSafeAreaPadding : !includeSafeAreaPaddingBottom; + const hasSmallScreenOfflineIndicatorBottomSafeAreaPadding = isUsingEdgeToEdgeMode ? enableEdgeToEdgeBottomSafeAreaPadding : !includeSafeAreaPaddingBottom; /** * This style includes the bottom safe area padding for the mobile offline indicator. @@ -379,42 +385,64 @@ function ScreenWrapper( * two overlapping layers of translucent background. * If the device does not have soft keys, the bottom safe area padding is applied as `paddingBottom`. */ - const mobileOfflineIndicatorBottomSafeAreaStyle = useBottomSafeSafeAreaPaddingStyle({ - addBottomSafeAreaPadding: hasMobileOfflineIndicatorBottomSafeAreaPadding, + const smallScreenOfflineIndicatorBottomSafeAreaStyle = useBottomSafeSafeAreaPaddingStyle({ + addBottomSafeAreaPadding: hasSmallScreenOfflineIndicatorBottomSafeAreaPadding, styleProperty: isSoftKeyNavigation ? 'bottom' : 'paddingBottom', }); /** If there is no bottom content, the mobile offline indicator will stick to the bottom of the screen by default. */ - const displayStickyMobileOfflineIndicator = shouldMobileOfflineIndicatorStickToBottom && !bottomContent; + const displayStickySmallScreenOfflineIndicator = shouldMobileOfflineIndicatorStickToBottom && !bottomContent; /** - * This style includes all styles applied to the container of the mobile offline indicator. + * This style includes all styles applied to the container of the offline indicator on small screens. * It always applies the bottom safe area padding as well as the background style, if the device has soft keys. * In this case, we want the whole container (including the bottom safe area padding) to have translucent/opaque background. */ - const mobileOfflineIndicatorContainerStyle = useMemo( - () => [mobileOfflineIndicatorBottomSafeAreaStyle, displayStickyMobileOfflineIndicator && styles.stickToBottom, !isSoftKeyNavigation && mobileOfflineIndicatorBackgroundStyle], - [mobileOfflineIndicatorBottomSafeAreaStyle, displayStickyMobileOfflineIndicator, styles.stickToBottom, isSoftKeyNavigation, mobileOfflineIndicatorBackgroundStyle], + const smallScreenOfflineIndicatorContainerStyle = useMemo( + () => [ + smallScreenOfflineIndicatorBottomSafeAreaStyle, + displayStickySmallScreenOfflineIndicator && styles.stickToBottom, + !isSoftKeyNavigation && smallScreenOfflineIndicatorBackgroundStyle, + ], + [smallScreenOfflineIndicatorBottomSafeAreaStyle, displayStickySmallScreenOfflineIndicator, styles.stickToBottom, isSoftKeyNavigation, smallScreenOfflineIndicatorBackgroundStyle], ); /** - * This style includes the styles applied to the mobile offline indicator component. - * If the device has soft keys, we only want to apply the background style to the mobile offline indicator component, + * This style includes the styles applied to the offline indicator component on small screens. + * If the device has soft keys, we only want to apply the background style to the offline indicator component, * rather than the whole container, because otherwise the navigation bar would be extra opaque, since it already has a translucent background. */ - const mobileOfflineIndicatorStyle = useMemo( - () => [styles.pl5, isSoftKeyNavigation && mobileOfflineIndicatorBackgroundStyle, offlineIndicatorStyle], - [isSoftKeyNavigation, mobileOfflineIndicatorBackgroundStyle, offlineIndicatorStyle, styles.pl5], + const smallScreenOfflineIndicatorStyle = useMemo( + () => [styles.pl5, isSoftKeyNavigation && smallScreenOfflineIndicatorBackgroundStyle, offlineIndicatorStyle], + [isSoftKeyNavigation, smallScreenOfflineIndicatorBackgroundStyle, offlineIndicatorStyle, styles.pl5], + ); + + // This context allows us to disable the safe area padding offseting the offline indicator in scrollable components like 'ScrollView', 'SelectionList' or 'FormProvider'. + // This is useful e.g. for the RightModalNavigator, where we want to avoid the safe area padding offseting the offline indicator because we only show the offline indicator on small screens. + const parentOfflineIndicatorContext = useContext(ScreenWrapperOfflineIndicatorContext); + const showSmallScreenOfflineIndicator = shouldShowOfflineIndicatorProp ?? parentOfflineIndicatorContext.showOnSmallScreens ?? true; + const showWideScreenOfflineIndicator = shouldShowOfflineIndicatorInWideScreenProp ?? parentOfflineIndicatorContext.showOnWideScreens ?? false; + const addOfflineIndicatorSafeAreaPadding = + disableOfflineIndicatorSafeAreaPaddingProp === undefined ? parentOfflineIndicatorContext.addSafeAreaPadding ?? true : !disableOfflineIndicatorSafeAreaPaddingProp; + + const offlineIndicatorContextValue = useMemo( + () => ({ + addSafeAreaPadding: addOfflineIndicatorSafeAreaPadding, + showOnSmallScreens: showSmallScreenOfflineIndicator, + showOnWideScreens: showWideScreenOfflineIndicator, + }), + [addOfflineIndicatorSafeAreaPadding, showSmallScreenOfflineIndicator, showWideScreenOfflineIndicator], ); - const displayMobileOfflineIndicator = isSmallScreenWidth && shouldShowOfflineIndicator; - const displayWidescreenOfflineIndicator = !shouldUseNarrowLayout && shouldShowOfflineIndicatorInWideScreen; + const displaySmallScreenOfflineIndicator = isSmallScreenWidth && showSmallScreenOfflineIndicator; + const displayWidescreenOfflineIndicator = !shouldUseNarrowLayout && showWideScreenOfflineIndicator; /** If we currently show the offline indicator and it has bottom safe area padding, we need to offset the bottom safe area padding in the KeyboardAvoidingView. */ - const shouldOffsetMobileOfflineIndicator = displayMobileOfflineIndicator && hasMobileOfflineIndicatorBottomSafeAreaPadding && isOffline; + const shouldOffsetMobileOfflineIndicator = displaySmallScreenOfflineIndicator && hasSmallScreenOfflineIndicatorBottomSafeAreaPadding && isOffline; const isAvoidingViewportScroll = useTackInputFocus(isFocused && shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && isMobileWebKit()); - const contextValue = useMemo( + + const statusContextValue = useMemo( () => ({didScreenTransitionEnd, isSafeAreaTopPaddingApplied, isSafeAreaBottomPaddingApplied: includeSafeAreaPaddingBottom}), [didScreenTransitionEnd, includeSafeAreaPaddingBottom, isSafeAreaTopPaddingApplied], ); @@ -449,38 +477,40 @@ function ScreenWrapper( > {isDevelopment && } - - { - // If props.children is a function, call it to provide the insets to the children. - typeof children === 'function' - ? children({ - insets, - safeAreaPaddingBottomStyle, - didScreenTransitionEnd, - }) - : children - } - {displayMobileOfflineIndicator && ( - <> - {isOffline && ( - - - {/* Since import state is tightly coupled to the offline state, it is safe to display it when showing offline indicator */} - - )} - - - )} - {displayWidescreenOfflineIndicator && ( - <> - - {/* Since import state is tightly coupled to the offline state, it is safe to display it when showing offline indicator */} - - - )} + + + { + // If props.children is a function, call it to provide the insets to the children. + typeof children === 'function' + ? children({ + insets, + safeAreaPaddingBottomStyle, + didScreenTransitionEnd, + }) + : children + } + {displaySmallScreenOfflineIndicator && ( + <> + {isOffline && ( + + + {/* Since import state is tightly coupled to the offline state, it is safe to display it when showing offline indicator */} + + )} + + + )} + {displayWidescreenOfflineIndicator && ( + <> + + {/* 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/useBottomSafeSafeAreaPaddingStyle.ts b/src/hooks/useBottomSafeSafeAreaPaddingStyle.ts index e680af8a73bd..123ec4ed8d3c 100644 --- a/src/hooks/useBottomSafeSafeAreaPaddingStyle.ts +++ b/src/hooks/useBottomSafeSafeAreaPaddingStyle.ts @@ -1,6 +1,7 @@ -import {useMemo} from 'react'; +import {useContext, useMemo} from 'react'; import {StyleSheet} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; +import ScreenWrapperOfflineIndicatorContext from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; import CONST from '@src/CONST'; import useNetwork from './useNetwork'; import useSafeAreaPaddings from './useSafeAreaPaddings'; @@ -29,11 +30,16 @@ type UseBottomSafeAreaPaddingStyleParams = { * @param params - The parameters for the hook. * @returns The style with bottom safe area padding applied. */ -function useBottomSafeSafeAreaPaddingStyle(params?: UseBottomSafeAreaPaddingStyleParams) { +function useBottomSafeSafeAreaPaddingStyle({ + addBottomSafeAreaPadding = false, + addOfflineIndicatorBottomSafeAreaPadding = false, + style, + styleProperty = 'paddingBottom', + additionalPaddingBottom = 0, +}: UseBottomSafeAreaPaddingStyleParams = {}) { const {paddingBottom: safeAreaPaddingBottom} = useSafeAreaPaddings(true); const {isOffline} = useNetwork(); - - const {addBottomSafeAreaPadding, addOfflineIndicatorBottomSafeAreaPadding, style, styleProperty = 'paddingBottom', additionalPaddingBottom = 0} = params ?? {}; + const {addSafeAreaPadding: isOfflineIndicatorSafeAreaPaddingEnabled} = useContext(ScreenWrapperOfflineIndicatorContext); return useMemo>(() => { let totalPaddingBottom: number | string = additionalPaddingBottom; @@ -43,7 +49,7 @@ function useBottomSafeSafeAreaPaddingStyle(params?: UseBottomSafeAreaPaddingStyl totalPaddingBottom += safeAreaPaddingBottom; } - if (addOfflineIndicatorBottomSafeAreaPadding && isOffline) { + if (addOfflineIndicatorBottomSafeAreaPadding && isOffline && isOfflineIndicatorSafeAreaPaddingEnabled) { totalPaddingBottom += CONST.OFFLINE_INDICATOR_HEIGHT; } @@ -71,7 +77,16 @@ function useBottomSafeSafeAreaPaddingStyle(params?: UseBottomSafeAreaPaddingStyl // If no style is provided, return the padding as an object return {paddingBottom: totalPaddingBottom}; - }, [additionalPaddingBottom, addBottomSafeAreaPadding, addOfflineIndicatorBottomSafeAreaPadding, isOffline, style, safeAreaPaddingBottom, styleProperty]); + }, [ + additionalPaddingBottom, + addBottomSafeAreaPadding, + addOfflineIndicatorBottomSafeAreaPadding, + isOffline, + isOfflineIndicatorSafeAreaPaddingEnabled, + style, + safeAreaPaddingBottom, + styleProperty, + ]); } export default useBottomSafeSafeAreaPaddingStyle; From f2708dfa6ec85f98107731396d8de13e3fcc20b5 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 15:12:57 +0200 Subject: [PATCH 09/29] fix: make offline indicator bottom safe area padding prop explicit --- src/components/OfflineIndicator.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index db5250ee8234..ea9f3aff1fac 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -31,6 +31,7 @@ function OfflineIndicator({style, containerStyles: containerStylesProp, addBotto const fallbackStyle = useMemo(() => [styles.offlineIndicatorContainer, containerStylesProp], [styles.offlineIndicatorContainer, containerStylesProp]); const containerStyles = useBottomSafeSafeAreaPaddingStyle({ addBottomSafeAreaPadding, + addOfflineIndicatorBottomSafeAreaPadding: false, style: fallbackStyle, }); From 447896aab1ffe5558983309b4a812870216ae20e Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 15:16:45 +0200 Subject: [PATCH 10/29] fix: simplfiy logic in RHP modal navigator --- .../Navigators/RightModalNavigator.tsx | 368 +++++++++--------- src/pages/NewChatPage.tsx | 3 +- src/pages/workspace/WorkspaceNewRoomPage.tsx | 4 - 3 files changed, 192 insertions(+), 183 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index d41864904711..4ccd8b0e3f1f 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -1,6 +1,8 @@ -import React, {useRef} from 'react'; +import React, {useMemo, useRef} from 'react'; import {InteractionManager, View} from 'react-native'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; +import type {ScreenWrapperOfflineIndicatorContextType} from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; +import ScreenWrapperOfflineIndicatorContext from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {abandonReviewDuplicateTransactions} from '@libs/actions/Transaction'; @@ -22,9 +24,17 @@ const Stack = createPlatformStackNavigator(); function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const isExecutingRef = useRef(false); + const offlineIndicatorContextValue: ScreenWrapperOfflineIndicatorContextType = useMemo( + () => ({ + addSafeAreaPadding: isSmallScreenWidth, + }), + [isSmallScreenWidth], + ); + const screenOptions = useCustomScreenOptions(); return ( @@ -43,184 +53,186 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { }} /> )} - - { - if ( - // @ts-expect-error There is something wrong with a types here and it's don't see the params list - navigation.getState().routes.find((routes) => routes.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR)?.params?.screen === - SCREENS.RIGHT_MODAL.TRANSACTION_DUPLICATE || - route.params?.screen !== SCREENS.RIGHT_MODAL.TRANSACTION_DUPLICATE - ) { - return; - } - // Delay clearing review duplicate data till the RHP is completely closed - // to avoid not found showing briefly in confirmation page when RHP is closing - InteractionManager.runAfterInteractions(() => { - abandonReviewDuplicateTransactions(); - }); - }, - }} - id={NAVIGATORS.RIGHT_MODAL_NAVIGATOR} - > - - { - InteractionManager.runAfterInteractions(clearTwoFactorAuthData); + + + { + if ( + // @ts-expect-error There is something wrong with a types here and it's don't see the params list + navigation.getState().routes.find((routes) => routes.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR)?.params?.screen === + SCREENS.RIGHT_MODAL.TRANSACTION_DUPLICATE || + route.params?.screen !== SCREENS.RIGHT_MODAL.TRANSACTION_DUPLICATE + ) { + return; + } + // Delay clearing review duplicate data till the RHP is completely closed + // to avoid not found showing briefly in confirmation page when RHP is closing + InteractionManager.runAfterInteractions(() => { + abandonReviewDuplicateTransactions(); + }); }, }} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id={NAVIGATORS.RIGHT_MODAL_NAVIGATOR} + > + + { + InteractionManager.runAfterInteractions(clearTwoFactorAuthData); + }, + }} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 9e79de13d7af..b930a766f7a1 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -340,6 +340,8 @@ function NewChatPage() { enableEdgeToEdgeBottomSafeAreaPadding includePaddingTop={false} shouldEnablePickerAvoiding={false} + disableOfflineIndicatorSafeAreaPadding + // shouldShowOfflineIndicator={false} keyboardVerticalOffset={variables.contentHeaderHeight + top + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding} // Disable the focus trap of this page to activate the parent focus trap in `NewChatSelectorPage`. focusTrapSettings={{active: false}} @@ -365,7 +367,6 @@ function NewChatPage() { initiallyFocusedOptionKey={firstKeyForList} shouldTextInputInterceptSwipe addBottomSafeAreaPadding - addOfflineIndicatorBottomSafeAreaPadding={false} /> ); diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 84724fa8ceb3..e76ee643c2c9 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -17,7 +17,6 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; @@ -43,8 +42,6 @@ function WorkspaceNewRoomPage() { const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false}); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to show offline indicator on small screen only - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth} = useResponsiveLayout(); const {top} = useSafeAreaInsets(); const [visibility, setVisibility] = useState>(CONST.REPORT.VISIBILITY.RESTRICTED); const [writeCapability, setWriteCapability] = useState>(CONST.REPORT.WRITE_CAPABILITIES.ALL); @@ -236,7 +233,6 @@ function WorkspaceNewRoomPage() { onSubmit={submit} enabledWhenOffline addBottomSafeAreaPadding - addOfflineIndicatorBottomSafeAreaPadding={isSmallScreenWidth} > Date: Mon, 5 May 2025 16:53:19 +0200 Subject: [PATCH 11/29] fix: disable rendering any nested offline indicator, except modals --- src/components/Modal/BaseModal.tsx | 130 +++++++++++++------------ src/components/ScreenWrapper/index.tsx | 7 +- 2 files changed, 72 insertions(+), 65 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index fd44f593f189..9ee9fe6fe4bf 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -6,6 +6,7 @@ import type {ValueOf} from 'type-fest'; import ColorSchemeWrapper from '@components/ColorSchemeWrapper'; import FocusTrapForModal from '@components/FocusTrap/FocusTrapForModal'; import NavigationBar from '@components/NavigationBar'; +import ScreenWrapperOfflineIndicatorContext from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; import useKeyboardState from '@hooks/useKeyboardState'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -27,6 +28,9 @@ import ModalContent from './ModalContent'; import ModalContext from './ModalContext'; import type BaseModalProps from './types'; +// In Modals we need to reset the ScreenWrapperOfflineIndicatorContext to allow nested ScreenWrapper components to render offline indicators. +const OFFLINE_INDICATOR_CONTEXT_RESET_IN_MODAL_VALUE = {}; + type ModalComponentProps = (ReactNativeModalProps | ModalProps) & { type?: ValueOf; shouldUseNewModal: boolean; @@ -251,74 +255,76 @@ function BaseModal( return ( - - 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={closeTop} - onModalShow={handleShowModal} - propagateSwipe={propagateSwipe} - onModalHide={hideModal} - onModalWillShow={saveFocusState} - onDismiss={handleDismissModal} - onSwipeComplete={() => onClose?.()} - swipeDirection={swipeDirection} - swipeThreshold={swipeThreshold} - isVisible={isVisible} - backdropColor={theme.overlay} - backdropOpacity={!shouldUseCustomBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} - backdropTransitionOutTiming={0} - hasBackdrop={fullscreen} - coverScreen={fullscreen} - style={[modalStyle, sidePanelStyle]} - deviceHeight={windowHeight} - deviceWidth={windowWidth} - animationIn={animationIn ?? modalStyleAnimationIn} - animationInDelay={animationInDelay} - animationOut={animationOut ?? modalStyleAnimationOut} - useNativeDriver={useNativeDriver} - useNativeDriverForBackdrop={useNativeDriverForBackdrop} - hideModalContentWhileAnimating={hideModalContentWhileAnimating} - animationInTiming={animationInTiming} - animationOutTiming={animationOutTiming} - statusBarTranslucent={statusBarTranslucent} - navigationBarTranslucent={navigationBarTranslucent} - onLayout={onLayout} - avoidKeyboard={avoidKeyboard} - customBackdrop={shouldUseCustomBackdrop ? : undefined} - type={type} - shouldUseNewModal={shouldUseNewModal} + + - 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={closeTop} + onModalShow={handleShowModal} + propagateSwipe={propagateSwipe} + onModalHide={hideModal} onModalWillShow={saveFocusState} onDismiss={handleDismissModal} + onSwipeComplete={() => onClose?.()} + swipeDirection={swipeDirection} + swipeThreshold={swipeThreshold} + isVisible={isVisible} + backdropColor={theme.overlay} + backdropOpacity={!shouldUseCustomBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} + backdropTransitionOutTiming={0} + hasBackdrop={fullscreen} + coverScreen={fullscreen} + style={[modalStyle, sidePanelStyle]} + deviceHeight={windowHeight} + deviceWidth={windowWidth} + animationIn={animationIn ?? modalStyleAnimationIn} + animationInDelay={animationInDelay} + animationOut={animationOut ?? modalStyleAnimationOut} + useNativeDriver={useNativeDriver} + useNativeDriverForBackdrop={useNativeDriverForBackdrop} + hideModalContentWhileAnimating={hideModalContentWhileAnimating} + animationInTiming={animationInTiming} + animationOutTiming={animationOutTiming} + statusBarTranslucent={statusBarTranslucent} + navigationBarTranslucent={navigationBarTranslucent} + onLayout={onLayout} + avoidKeyboard={avoidKeyboard} + customBackdrop={shouldUseCustomBackdrop ? : undefined} + type={type} + shouldUseNewModal={shouldUseNewModal} > - - - {children} - - - - {!keyboardStateContextValue?.isKeyboardActive && } - - + + {children} + + + + {!keyboardStateContextValue?.isKeyboardActive && } + + + ); } diff --git a/src/components/ScreenWrapper/index.tsx b/src/components/ScreenWrapper/index.tsx index 7f656f6dee3d..6b3c1f713901 100644 --- a/src/components/ScreenWrapper/index.tsx +++ b/src/components/ScreenWrapper/index.tsx @@ -428,10 +428,11 @@ function ScreenWrapper( const offlineIndicatorContextValue = useMemo( () => ({ addSafeAreaPadding: addOfflineIndicatorSafeAreaPadding, - showOnSmallScreens: showSmallScreenOfflineIndicator, - showOnWideScreens: showWideScreenOfflineIndicator, + // Prevent any nested ScreenWrapper components from rendering another offline indicator. + showOnSmallScreens: false, + showOnWideScreens: false, }), - [addOfflineIndicatorSafeAreaPadding, showSmallScreenOfflineIndicator, showWideScreenOfflineIndicator], + [addOfflineIndicatorSafeAreaPadding], ); const displaySmallScreenOfflineIndicator = isSmallScreenWidth && showSmallScreenOfflineIndicator; From 1c722e8d4205e68405b1a037ca5c4a2f12fc95b2 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 16:53:52 +0200 Subject: [PATCH 12/29] fix: offline indicators on NewChat and NewRoom pages --- src/pages/NewChatPage.tsx | 2 +- src/pages/workspace/WorkspaceNewRoomPage.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index b930a766f7a1..1b3c92f379d6 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -341,7 +341,7 @@ function NewChatPage() { includePaddingTop={false} shouldEnablePickerAvoiding={false} disableOfflineIndicatorSafeAreaPadding - // shouldShowOfflineIndicator={false} + shouldShowOfflineIndicator={false} keyboardVerticalOffset={variables.contentHeaderHeight + top + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding} // Disable the focus trap of this page to activate the parent focus trap in `NewChatSelectorPage`. focusTrapSettings={{active: false}} diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index e76ee643c2c9..12d6e3df2ba3 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -215,6 +215,7 @@ function WorkspaceNewRoomPage() { Date: Mon, 5 May 2025 16:54:09 +0200 Subject: [PATCH 13/29] simplify RHP offline indicator setting --- .../AppNavigator/Navigators/RightModalNavigator.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 4ccd8b0e3f1f..ec1dc6ec2d6d 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -24,15 +24,14 @@ const Stack = createPlatformStackNavigator(); function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { const styles = useThemeStyles(); - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const isExecutingRef = useRef(false); const offlineIndicatorContextValue: ScreenWrapperOfflineIndicatorContextType = useMemo( () => ({ - addSafeAreaPadding: isSmallScreenWidth, + addSafeAreaPadding: shouldUseNarrowLayout, }), - [isSmallScreenWidth], + [shouldUseNarrowLayout], ); const screenOptions = useCustomScreenOptions(); From e5c45cf3846fa5645a77e90b493aaa34f66e4115 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 17:02:26 +0200 Subject: [PATCH 14/29] feat: extract narrow pane context provider and apply to other narrow pane navigators --- .../Navigators/LeftModalNavigator.tsx | 25 +++++++++++-------- ...rowPaneOfflineIndicatorContextProvider.tsx | 20 +++++++++++++++ .../Navigators/PublicRightModalNavigator.tsx | 25 +++++++++++-------- .../Navigators/RightModalNavigator.tsx | 16 +++--------- 4 files changed, 52 insertions(+), 34 deletions(-) create mode 100644 src/libs/Navigation/AppNavigator/Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx diff --git a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx index aaf40a62f99e..c0d9a1915eac 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx @@ -10,6 +10,7 @@ import type {AuthScreensParamList, LeftModalNavigatorParamList} from '@libs/Navi import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; import type ReactComponentModule from '@src/types/utils/ReactComponentModule'; +import NarrowPaneOfflineIndicatorContextProvider from './NarrowPaneOfflineIndicatorContextProvider'; import Overlay from './Overlay'; type LeftModalNavigatorProps = PlatformStackScreenProps; @@ -31,17 +32,19 @@ function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { onPress={navigation.goBack} /> )} - - - - - + + + + + + + ); } diff --git a/src/libs/Navigation/AppNavigator/Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx b/src/libs/Navigation/AppNavigator/Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx new file mode 100644 index 000000000000..9de60bcd3b01 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx @@ -0,0 +1,20 @@ +import {useMemo} from 'react'; +import type {ScreenWrapperOfflineIndicatorContextType} from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; +import ScreenWrapperOfflineIndicatorContext from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; + +function NarrowPaneOfflineIndicatorContextProvider({children}: ChildrenProps) { + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + const offlineIndicatorContextValue: ScreenWrapperOfflineIndicatorContextType = useMemo( + () => ({ + addSafeAreaPadding: shouldUseNarrowLayout, + }), + [shouldUseNarrowLayout], + ); + + return {children}; +} + +export default NarrowPaneOfflineIndicatorContextProvider; diff --git a/src/libs/Navigation/AppNavigator/Navigators/PublicRightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/PublicRightModalNavigator.tsx index dccf2e10fc75..9a330e729ee1 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/PublicRightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/PublicRightModalNavigator.tsx @@ -10,6 +10,7 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig import type {ConsoleNavigatorParamList, PublicScreensParamList} from '@libs/Navigation/types'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; +import NarrowPaneOfflineIndicatorContextProvider from './NarrowPaneOfflineIndicatorContextProvider'; import Overlay from './Overlay'; type PublicRightModalNavigatorComponentProps = PlatformStackScreenProps; @@ -25,17 +26,19 @@ function PublicRightModalNavigatorComponent({navigation}: PublicRightModalNaviga return ( {!shouldUseNarrowLayout && } - - - - - + + + + + + + ); } diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index ec1dc6ec2d6d..47790b4eb1cf 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -1,8 +1,6 @@ -import React, {useMemo, useRef} from 'react'; +import React, {useRef} from 'react'; import {InteractionManager, View} from 'react-native'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; -import type {ScreenWrapperOfflineIndicatorContextType} from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; -import ScreenWrapperOfflineIndicatorContext from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {abandonReviewDuplicateTransactions} from '@libs/actions/Transaction'; @@ -16,6 +14,7 @@ import type {AuthScreensParamList, RightModalNavigatorParamList} from '@navigati import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; +import NarrowPaneOfflineIndicatorContextProvider from './NarrowPaneOfflineIndicatorContextProvider'; import Overlay from './Overlay'; type RightModalNavigatorProps = PlatformStackScreenProps; @@ -27,13 +26,6 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); const isExecutingRef = useRef(false); - const offlineIndicatorContextValue: ScreenWrapperOfflineIndicatorContextType = useMemo( - () => ({ - addSafeAreaPadding: shouldUseNarrowLayout, - }), - [shouldUseNarrowLayout], - ); - const screenOptions = useCustomScreenOptions(); return ( @@ -52,7 +44,7 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { }} /> )} - + - + ); } From 6a139011c040e8037acb4b26b53bd6c7f1769f3d Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 17:09:22 +0200 Subject: [PATCH 15/29] import React --- .../Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx b/src/libs/Navigation/AppNavigator/Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx index 9de60bcd3b01..2b69525a9dd6 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/NarrowPaneOfflineIndicatorContextProvider.tsx @@ -1,4 +1,4 @@ -import {useMemo} from 'react'; +import React, {useMemo} from 'react'; import type {ScreenWrapperOfflineIndicatorContextType} from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; import ScreenWrapperOfflineIndicatorContext from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; From 1bfd2e72c3f5dc60345f120dfe6c2c7b4bc26d30 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 17:23:22 +0200 Subject: [PATCH 16/29] fix: apply offline indicator bottom safe area padding by default --- src/hooks/useBottomSafeSafeAreaPaddingStyle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useBottomSafeSafeAreaPaddingStyle.ts b/src/hooks/useBottomSafeSafeAreaPaddingStyle.ts index 123ec4ed8d3c..0b2912ab5470 100644 --- a/src/hooks/useBottomSafeSafeAreaPaddingStyle.ts +++ b/src/hooks/useBottomSafeSafeAreaPaddingStyle.ts @@ -32,7 +32,7 @@ type UseBottomSafeAreaPaddingStyleParams = { */ function useBottomSafeSafeAreaPaddingStyle({ addBottomSafeAreaPadding = false, - addOfflineIndicatorBottomSafeAreaPadding = false, + addOfflineIndicatorBottomSafeAreaPadding = addBottomSafeAreaPadding, style, styleProperty = 'paddingBottom', additionalPaddingBottom = 0, From ae958d9aca282435dcda29ca7d9b1bb4d50a3da7 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 17:25:29 +0200 Subject: [PATCH 17/29] refactor: remove default `addBottomSafeAreaPadding` values when we default to false --- src/components/BlockingViews/BlockingView.tsx | 4 ++-- src/components/FixedFooter.tsx | 8 +------- src/components/Form/FormWrapper.tsx | 4 ++-- src/components/ScrollView.tsx | 9 +-------- src/components/SectionList/BaseSectionList.tsx | 7 +------ src/components/SelectionList/BaseSelectionList.tsx | 4 ++-- 6 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/components/BlockingViews/BlockingView.tsx b/src/components/BlockingViews/BlockingView.tsx index ee800c400a93..feeb64856a8c 100644 --- a/src/components/BlockingViews/BlockingView.tsx +++ b/src/components/BlockingViews/BlockingView.tsx @@ -109,8 +109,8 @@ function BlockingView({ CustomSubtitle, contentFitImage, containerStyle: containerStyleProp, - addBottomSafeAreaPadding = false, - addOfflineIndicatorBottomSafeAreaPadding = addBottomSafeAreaPadding, + addBottomSafeAreaPadding, + addOfflineIndicatorBottomSafeAreaPadding, testID, }: BlockingViewProps) { const styles = useThemeStyles(); diff --git a/src/components/FixedFooter.tsx b/src/components/FixedFooter.tsx index 2a17997324f3..c5910647c705 100644 --- a/src/components/FixedFooter.tsx +++ b/src/components/FixedFooter.tsx @@ -22,13 +22,7 @@ type FixedFooterProps = { shouldStickToBottom?: boolean; }; -function FixedFooter({ - style, - children, - addBottomSafeAreaPadding = false, - addOfflineIndicatorBottomSafeAreaPadding = addBottomSafeAreaPadding, - shouldStickToBottom = false, -}: FixedFooterProps) { +function FixedFooter({style, children, addBottomSafeAreaPadding, addOfflineIndicatorBottomSafeAreaPadding, shouldStickToBottom = false}: FixedFooterProps) { const styles = useThemeStyles(); const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({ diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 3120c5a3fffb..c2730c1e5ea7 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -83,7 +83,7 @@ function FormWrapper({ isLoading = false, shouldScrollToEnd = false, addBottomSafeAreaPadding, - addOfflineIndicatorBottomSafeAreaPadding: addOfflineIndicatorBottomSafeAreaPaddingProp, + addOfflineIndicatorBottomSafeAreaPadding, shouldSubmitButtonStickToBottom: shouldSubmitButtonStickToBottomProp, shouldSubmitButtonBlendOpacity = false, }: FormWrapperProps) { @@ -137,13 +137,13 @@ function FormWrapper({ // If the paddingBottom is 0, it has already been applied to a parent component and we don't want to apply the padding again. const isLegacyBottomSafeAreaPaddingAlreadyApplied = paddingBottom === 0; const shouldApplyBottomSafeAreaPadding = addBottomSafeAreaPadding ?? !isLegacyBottomSafeAreaPaddingAlreadyApplied; - const addOfflineIndicatorBottomSafeAreaPadding = addOfflineIndicatorBottomSafeAreaPaddingProp ?? addBottomSafeAreaPadding === true; // We need to add bottom safe area padding to the submit button when we don't use a scroll view or // when the submit button is sticking to the bottom. const addSubmitButtonBottomSafeAreaPadding = addBottomSafeAreaPadding && (!shouldUseScrollView || shouldSubmitButtonStickToBottom); const submitButtonStylesWithBottomSafeAreaPadding = useBottomSafeSafeAreaPaddingStyle({ addBottomSafeAreaPadding: addSubmitButtonBottomSafeAreaPadding, + addOfflineIndicatorBottomSafeAreaPadding, styleProperty: shouldSubmitButtonStickToBottom ? 'bottom' : 'paddingBottom', additionalPaddingBottom: shouldSubmitButtonStickToBottom ? styles.pb5.paddingBottom : 0, style: submitButtonStyles, diff --git a/src/components/ScrollView.tsx b/src/components/ScrollView.tsx index 660f43114ca6..16fd2dd167ea 100644 --- a/src/components/ScrollView.tsx +++ b/src/components/ScrollView.tsx @@ -14,14 +14,7 @@ type ScrollViewProps = RNScrollViewProps & { }; function ScrollView( - { - children, - scrollIndicatorInsets, - contentContainerStyle: contentContainerStyleProp, - addBottomSafeAreaPadding = false, - addOfflineIndicatorBottomSafeAreaPadding = addBottomSafeAreaPadding, - ...props - }: ScrollViewProps, + {children, scrollIndicatorInsets, contentContainerStyle: contentContainerStyleProp, addBottomSafeAreaPadding, addOfflineIndicatorBottomSafeAreaPadding, ...props}: ScrollViewProps, ref: ForwardedRef, ) { const contentContainerStyle = useBottomSafeSafeAreaPaddingStyle({ diff --git a/src/components/SectionList/BaseSectionList.tsx b/src/components/SectionList/BaseSectionList.tsx index 18a915d1a58b..eb39de8bd541 100644 --- a/src/components/SectionList/BaseSectionList.tsx +++ b/src/components/SectionList/BaseSectionList.tsx @@ -4,12 +4,7 @@ import AnimatedSectionList from './AnimatedSectionList'; import type {SectionListProps, SectionListRef} from './types'; function BaseSectionList( - { - addBottomSafeAreaPadding = false, - addOfflineIndicatorBottomSafeAreaPadding = addBottomSafeAreaPadding, - contentContainerStyle: contentContainerStyleProp, - ...restProps - }: SectionListProps, + {addBottomSafeAreaPadding, addOfflineIndicatorBottomSafeAreaPadding, contentContainerStyle: contentContainerStyleProp, ...restProps}: SectionListProps, ref: SectionListRef, ) { const contentContainerStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding, addOfflineIndicatorBottomSafeAreaPadding, style: contentContainerStyleProp}); diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index c516762fc044..60e22bfc6022 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -135,8 +135,8 @@ function BaseSelectionList( listItemTitleContainerStyles, isScreenFocused = false, shouldSubscribeToArrowKeyEvents = true, - addBottomSafeAreaPadding = false, - addOfflineIndicatorBottomSafeAreaPadding = addBottomSafeAreaPadding, + addBottomSafeAreaPadding, + addOfflineIndicatorBottomSafeAreaPadding, fixedNumItemsForLoader, loaderSpeed, errorText, From b2ab9aaac0435a725b6fcd8173126bac2ae379da Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 17:25:44 +0200 Subject: [PATCH 18/29] fix: don't apply offline indicator padding in ScreenWrapper styles --- src/components/ScreenWrapper/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ScreenWrapper/index.tsx b/src/components/ScreenWrapper/index.tsx index 6b3c1f713901..25376d1df6e3 100644 --- a/src/components/ScreenWrapper/index.tsx +++ b/src/components/ScreenWrapper/index.tsx @@ -339,7 +339,7 @@ function ScreenWrapper( }, [isUsingEdgeToEdgeMode, ignoreInsetsConsumption, includePaddingTop, paddingTop, unmodifiedPaddings.top]); const showBottomContent = isUsingEdgeToEdgeMode ? !!bottomContent : true; - const edgeToEdgeBottomContentStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: true}); + const edgeToEdgeBottomContentStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: true, addOfflineIndicatorBottomSafeAreaPadding: false}); const legacyBottomContentStyle: StyleProp = useMemo(() => { const shouldUseUnmodifiedPaddings = includeSafeAreaPaddingBottom && ignoreInsetsConsumption; if (shouldUseUnmodifiedPaddings) { @@ -387,6 +387,7 @@ function ScreenWrapper( */ const smallScreenOfflineIndicatorBottomSafeAreaStyle = useBottomSafeSafeAreaPaddingStyle({ addBottomSafeAreaPadding: hasSmallScreenOfflineIndicatorBottomSafeAreaPadding, + addOfflineIndicatorBottomSafeAreaPadding: false, styleProperty: isSoftKeyNavigation ? 'bottom' : 'paddingBottom', }); From bf87b7d5eabb32bd49342c94186af39fe32dab7e Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 5 May 2025 17:26:41 +0200 Subject: [PATCH 19/29] fix: extract EmptyWorkspaceView to make automatic handling work --- src/pages/workspace/WorkspaceNewRoomPage.tsx | 50 +++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 12d6e3df2ba3..6ceab0de6b33 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -33,6 +33,33 @@ import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NewRoomForm'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; +function EmptyWorkspaceView() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: true, additionalPaddingBottom: styles.mb5.marginBottom, styleProperty: 'marginBottom'}); + + return ( + <> + +