diff --git a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx index ddb3207cdf2f1..eec9c3e01fdde 100644 --- a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx +++ b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx @@ -1,21 +1,49 @@ import React from 'react'; import {View} from 'react-native'; +import Onyx from 'react-native-onyx'; import NavigationTabBar from '@components/Navigation/NavigationTabBar'; import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS'; import TopBar from '@components/Navigation/TopBar'; +import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; +import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {isMobile} from '@libs/Browser'; +import ONYXKEYS from '@src/ONYXKEYS'; import SidebarLinksData from './SidebarLinksData'; +// Once the app finishes loading for the first time, we never show the skeleton again +// (even if isLoadingApp briefly flips back to true during a reconnect). +// This uses a module-level variable + connectWithoutView instead of a ref because +// a ref resets on unmount, so the skeleton would flash again when the component +// remounts (e.g. navigating between tabs). +let hasEverFinishedLoading = false; +Onyx.connectWithoutView({ + key: ONYXKEYS.IS_LOADING_APP, + callback: (value) => { + if (value !== false) { + return; + } + hasEverFinishedLoading = true; + }, +}); + function BaseSidebarScreen() { const styles = useThemeStyles(); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const shouldDisplayLHB = !shouldUseNarrowLayout; + const [isLoadingApp = true] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const shouldShowSkeleton = isLoadingApp && !hasEverFinishedLoading; + + // Must be called unconditionally so openApp() can proceed even when + // the skeleton is shown and SidebarLinksData has not mounted yet. + useConfirmReadyToOpenApp(); + return ( - - - + {shouldShowSkeleton ? : } {shouldDisplayLHB && } )} diff --git a/src/pages/inbox/sidebar/SidebarLinks.tsx b/src/pages/inbox/sidebar/SidebarLinks.tsx index ca50f7672fc14..e0a1128474b07 100644 --- a/src/pages/inbox/sidebar/SidebarLinks.tsx +++ b/src/pages/inbox/sidebar/SidebarLinks.tsx @@ -6,6 +6,7 @@ import type {ValueOf} from 'type-fest'; import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp'; +import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -14,6 +15,7 @@ import Navigation from '@libs/Navigation/Navigation'; import {cancelSpan} from '@libs/telemetry/activeSpans'; import * as ReportActionContextMenu from '@pages/inbox/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; @@ -24,9 +26,6 @@ type SidebarLinksProps = { /** List of options to display */ optionListItems: Report[]; - /** Whether the reports are loading. When false it means they are ready to be used. */ - isLoading: OnyxEntry; - /** The chat priority mode */ priorityMode?: OnyxEntry>; @@ -34,10 +33,11 @@ type SidebarLinksProps = { isActiveReport: (reportID: string) => boolean; }; -function SidebarLinks({insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) { +function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const [isLoadingReportData = true] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); useConfirmReadyToOpenApp(); @@ -90,7 +90,7 @@ function SidebarLinks({insets, optionListItems, isLoading, priorityMode = CONST. optionMode={viewMode} onFirstItemRendered={setSidebarLoaded} /> - {!!isLoading && optionListItems?.length === 0 && ( + {!!isLoadingReportData && optionListItems?.length === 0 && ( diff --git a/src/pages/inbox/sidebar/SidebarLinksData.tsx b/src/pages/inbox/sidebar/SidebarLinksData.tsx index b5b489af85a3b..c47562b8b2772 100644 --- a/src/pages/inbox/sidebar/SidebarLinksData.tsx +++ b/src/pages/inbox/sidebar/SidebarLinksData.tsx @@ -21,7 +21,6 @@ function SidebarLinksData({insets}: SidebarLinksDataProps) { const isFocused = useIsFocused(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const [isLoadingApp = true] = useOnyx(ONYXKEYS.IS_LOADING_APP); const [priorityMode = CONST.PRIORITY_MODE.DEFAULT] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); const {orderedReports, currentReportID} = useSidebarOrderedReportsState('SidebarLinksData'); @@ -62,7 +61,6 @@ function SidebarLinksData({insets}: SidebarLinksDataProps) { priorityMode={priorityMode ?? CONST.PRIORITY_MODE.DEFAULT} // Data props: isActiveReport={isActiveReport} - isLoading={isLoadingApp ?? false} optionListItems={orderedReports} /> diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index c29b6f3434d04..110f33cb4cfaf 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -197,6 +197,7 @@ function signInAndGetApp(reportName = '', participantAccountIDs?: number[]): Pro // Simulate setting an unread report and personal details await act(async () => { await Promise.all([ + Onyx.merge(ONYXKEYS.IS_LOADING_APP, false), Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { reportID: REPORT_ID, reportName, diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx index d2bebf6fca7ab..48326835cd7ab 100644 --- a/tests/ui/PaginationTest.tsx +++ b/tests/ui/PaginationTest.tsx @@ -232,6 +232,7 @@ async function signInAndGetApp(): Promise { await act(async () => { await Promise.all([ + Onyx.merge(ONYXKEYS.IS_LOADING_APP, false), // Simulate setting an unread report and personal details Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { reportID: REPORT_ID, diff --git a/tests/ui/SessionTest.tsx b/tests/ui/SessionTest.tsx index 00221d6945fa5..6f3b64b28b4a5 100644 --- a/tests/ui/SessionTest.tsx +++ b/tests/ui/SessionTest.tsx @@ -95,6 +95,7 @@ describe('Deep linking', () => { jest.spyOn(AppActions, 'openApp').mockImplementation(async () => { const originalResult = await originalOpenApp(); await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false); return originalResult; }); }); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index c4fb2ce87997c..9f748bac66048 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -215,6 +215,7 @@ async function signInAndGetAppWithUnreadChat(): Promise { }; await Promise.all([ + Onyx.merge(ONYXKEYS.IS_LOADING_APP, false), Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails), Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report), Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, reportActions),