diff --git a/src/CONST.ts b/src/CONST.ts
index 648e1de15b40..ddf9ebad5b66 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -314,6 +314,9 @@ const CONST = {
ANIMATED_HIGHLIGHT_END_DURATION: 2000,
ANIMATED_TRANSITION: 300,
ANIMATED_TRANSITION_FROM_VALUE: 100,
+ ANIMATED_PROGRESS_BAR_DELAY: 300,
+ ANIMATED_PROGRESS_BAR_OPACITY_DURATION: 300,
+ ANIMATED_PROGRESS_BAR_DURATION: 750,
ANIMATION_IN_TIMING: 100,
ANIMATION_DIRECTION: {
IN: 'in',
diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx
new file mode 100644
index 000000000000..163ffe2aa66b
--- /dev/null
+++ b/src/components/LoadingBar.tsx
@@ -0,0 +1,85 @@
+import React, {useEffect} from 'react';
+import Animated, {cancelAnimation, Easing, runOnJS, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated';
+import useThemeStyles from '@hooks/useThemeStyles';
+import CONST from '@src/CONST';
+
+type LoadingBarProps = {
+ // Whether or not to show the loading bar
+ shouldShow: boolean;
+};
+
+function LoadingBar({shouldShow}: LoadingBarProps) {
+ const left = useSharedValue(0);
+ const width = useSharedValue(0);
+ const opacity = useSharedValue(0);
+ const isVisible = useSharedValue(false);
+ const styles = useThemeStyles();
+
+ useEffect(() => {
+ if (shouldShow) {
+ // eslint-disable-next-line react-compiler/react-compiler
+ isVisible.value = true;
+ left.value = 0;
+ width.value = 0;
+ opacity.value = withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION});
+ left.value = withDelay(
+ CONST.ANIMATED_PROGRESS_BAR_DELAY,
+ withRepeat(
+ withSequence(
+ withTiming(0, {duration: 0}),
+ withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}),
+ withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}),
+ ),
+ -1,
+ false,
+ ),
+ );
+
+ width.value = withDelay(
+ CONST.ANIMATED_PROGRESS_BAR_DELAY,
+ withRepeat(
+ withSequence(
+ withTiming(0, {duration: 0}),
+ withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}),
+ withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}),
+ ),
+ -1,
+ false,
+ ),
+ );
+ } else if (isVisible.value) {
+ opacity.value = withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => {
+ runOnJS(() => {
+ isVisible.value = false;
+ cancelAnimation(left);
+ cancelAnimation(width);
+ });
+ });
+ }
+ // we want to update only when shouldShow changes
+ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
+ }, [shouldShow]);
+
+ const animatedIndicatorStyle = useAnimatedStyle(() => {
+ return {
+ left: `${left.value}%`,
+ width: `${width.value}%`,
+ };
+ });
+
+ const animatedContainerStyle = useAnimatedStyle(() => {
+ return {
+ opacity: opacity.value,
+ };
+ });
+
+ return (
+
+ {isVisible.value ? : null}
+
+ );
+}
+
+LoadingBar.displayName = 'ProgressBar';
+
+export default LoadingBar;
diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index 4c3ed5c705a5..0ca9dcdc2de3 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -10,6 +10,7 @@ import {useOnyx} from 'react-native-onyx';
import Banner from '@components/Banner';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import DragAndDropProvider from '@components/DragAndDrop/Provider';
+import LoadingBar from '@components/LoadingBar';
import MoneyReportHeader from '@components/MoneyReportHeader';
import MoneyRequestHeader from '@components/MoneyRequestHeader';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
@@ -129,6 +130,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const [workspaceTooltip] = useOnyx(ONYXKEYS.NVP_WORKSPACE_TOOLTIP);
const wasLoadingApp = usePrevious(isLoadingApp);
+ const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true});
const finishedLoadingApp = wasLoadingApp && !isLoadingApp;
const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction);
const prevIsDeletedParentAction = usePrevious(isDeletedParentAction);
@@ -756,6 +758,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
needsOffscreenAlphaCompositing
>
{headerView}
+ {shouldUseNarrowLayout && !!isLoadingReportData && }
{!!report && ReportUtils.isTaskReport(report) && shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && (
diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx
index e77f2000b85f..77c21d4ab2e1 100644
--- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx
+++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx
@@ -1,6 +1,7 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
+import LoadingBar from '@components/LoadingBar';
import ScreenWrapper from '@components/ScreenWrapper';
import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState';
import useLocalize from '@hooks/useLocalize';
@@ -31,6 +32,8 @@ function BaseSidebarScreen() {
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [activeWorkspace] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID ?? -1}`);
+ const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true});
+
useEffect(() => {
Performance.markStart(CONST.TIMING.SIDEBAR_LOADED);
Timing.start(CONST.TIMING.SIDEBAR_LOADED);
@@ -62,6 +65,7 @@ function BaseSidebarScreen() {
activeWorkspaceID={activeWorkspaceID}
shouldDisplaySearch={shouldDisplaySearch}
/>
+
left: 12,
},
+ progressBarWrapper: {
+ height: 2,
+ width: '100%',
+ backgroundColor: theme.border,
+ borderRadius: 5,
+ overflow: 'hidden',
+ },
+
+ progressBar: {
+ height: '100%',
+ backgroundColor: theme.success,
+ width: '100%',
+ },
+
qbdSetupLinkBox: {
backgroundColor: theme.hoverComponentBG,
borderRadius: variables.componentBorderRadiusMedium,