diff --git a/src/CONST.ts b/src/CONST.ts
index 3dfa6b4b078d..840743a27f65 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1449,6 +1449,8 @@ const CONST = {
WARM: 'warm',
REPORT_ACTION_ITEM_LAYOUT_DEBOUNCE_TIME: 1500,
SHOW_LOADING_SPINNER_DEBOUNCE_TIME: 250,
+ SKELETON_FADE_DURATION: 300,
+ SKELETON_SLIDE_DURATION: 1200,
TEST_TOOLS_MODAL_THROTTLE_TIME: 800,
TOOLTIP_SENSE: 1000,
TRIE_INITIALIZATION: 'trie_initialization',
diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx
index e6d1ec0cd66d..e13702517e10 100644
--- a/src/components/LoadingBar.tsx
+++ b/src/components/LoadingBar.tsx
@@ -1,83 +1,68 @@
import React, {useEffect} from 'react';
-import Animated, {cancelAnimation, Easing, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated';
+import {View} from 'react-native';
+import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import useThemeStyles from '@hooks/useThemeStyles';
+import colors from '@styles/theme/colors';
import CONST from '@src/CONST';
type LoadingBarProps = {
- // Whether or not to show the loading bar
+ // Whether to show the loading bar
shouldShow: boolean;
};
function LoadingBar({shouldShow}: LoadingBarProps) {
- const left = useSharedValue(0);
- const width = useSharedValue(0);
+ const left = useSharedValue(-30);
const opacity = useSharedValue(0);
+ const isAnimating = useSharedValue(false);
const styles = useThemeStyles();
useEffect(() => {
- if (shouldShow) {
- // eslint-disable-next-line react-compiler/react-compiler
- left.set(0);
- width.set(0);
- opacity.set(withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}));
+ if (shouldShow && !isAnimating.get()) {
+ isAnimating.set(true);
+ opacity.set(withTiming(1, {duration: CONST.TIMING.SKELETON_FADE_DURATION}));
left.set(
- 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.set(
- 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,
- ),
- ),
+ withTiming(100, {duration: CONST.TIMING.SKELETON_SLIDE_DURATION}, () => {
+ requestAnimationFrame(() => {
+ if (shouldShow) {
+ left.set(-30);
+ left.set(withTiming(100, {duration: CONST.TIMING.SKELETON_SLIDE_DURATION}));
+ } else {
+ opacity.set(
+ withTiming(0, {duration: CONST.TIMING.SKELETON_FADE_DURATION}, () => {
+ isAnimating.set(false);
+ }),
+ );
+ }
+ });
+ }),
);
- } else {
+ } else if (!shouldShow) {
opacity.set(
- withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => {
- cancelAnimation(left);
- cancelAnimation(width);
+ withTiming(0, {duration: CONST.TIMING.SKELETON_FADE_DURATION}, () => {
+ isAnimating.set(false);
}),
);
}
- // we want to update only when shouldShow changes
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [shouldShow]);
- const animatedIndicatorStyle = useAnimatedStyle(() => ({
+ const barStyle = useAnimatedStyle(() => ({
left: `${left.get()}%`,
- width: `${width.get()}%`,
- }));
-
- const animatedContainerStyle = useAnimatedStyle(() => ({
+ width: '30%',
+ height: '100%',
+ backgroundColor: colors.green,
opacity: opacity.get(),
+ borderRadius: 2,
}));
return (
-
-
-
+
+
+
);
}
-LoadingBar.displayName = 'ProgressBar';
+LoadingBar.displayName = 'LoadingBar';
export default LoadingBar;
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 97ee16ff0015..5713e97aca82 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -75,7 +75,6 @@ import DelegateNoAccessModal from './DelegateNoAccessModal';
import HeaderWithBackButton from './HeaderWithBackButton';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
-import LoadingBar from './LoadingBar';
import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar';
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
@@ -225,7 +224,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout);
const {isDelegateAccessRestricted} = useDelegateUserDetails();
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
- const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA);
const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP;
const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth;
@@ -520,7 +518,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
)}
)}
-
{isHoldMenuVisible && requestType !== undefined && (
)}
-
);
}
diff --git a/src/components/ReportActionsSkeletonView/SkeletonViewLines.tsx b/src/components/ReportActionsSkeletonView/SkeletonViewLines.tsx
index c4c2a3f43eb3..d3ea26ab5880 100644
--- a/src/components/ReportActionsSkeletonView/SkeletonViewLines.tsx
+++ b/src/components/ReportActionsSkeletonView/SkeletonViewLines.tsx
@@ -20,6 +20,7 @@ function SkeletonViewLines({numberOfRows, shouldAnimate = true}: SkeletonViewLin
height={CONST.CHAT_SKELETON_VIEW.HEIGHT_FOR_ROW_COUNT[numberOfRows]}
backgroundColor={theme.skeletonLHNIn}
foregroundColor={theme.skeletonLHNOut}
+ speed={3}
style={styles.mr5}
>
, p
DebugUtils.stringifyJSON({
actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT,
reportID,
- reportActionID: NumberUtils.rand64(),
+ reportActionID: rand64(),
created: DateUtils.getDBTime(),
actorAccountID: session?.accountID,
avatar: (session?.accountID && personalDetailsList?.[session.accountID]?.avatar) ?? '',
@@ -78,7 +78,7 @@ function DebugReportActionCreatePage({
{({safeAreaPaddingBottomStyle}) => (
@@ -107,7 +107,6 @@ function DebugReportActionCreatePage({
{
- const IOUTransactionID = ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID : -1;
+ const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : CONST.DEFAULT_NUMBER_ID;
return IOUTransactionID === props.transaction?.transactionID;
});
@@ -33,9 +34,8 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) {
)}
-
{shouldShowEarlyDiscountBanner && (
;
- /** Array of report actions for the report for this action */
- // eslint-disable-next-line react/no-unused-prop-types
- reportActions: OnyxTypes.ReportAction[];
-
/** Report action belonging to the report's parent */
parentReportAction: OnyxEntry;
@@ -1297,7 +1293,6 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => {
prevProps.linkedReportActionID === nextProps.linkedReportActionID &&
lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) &&
lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) &&
- lodashIsEqual(prevProps.reportActions, nextProps.reportActions) &&
lodashIsEqual(prevParentReportAction, nextParentReportAction) &&
prevProps.draftMessage === nextProps.draftMessage &&
prevProps.iouReport?.reportID === nextProps.iouReport?.reportID &&
diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx
index b7134fd7059e..5d3284d7bee8 100644
--- a/src/pages/home/report/ReportActionItemParentAction.tsx
+++ b/src/pages/home/report/ReportActionItemParentAction.tsx
@@ -36,9 +36,6 @@ type ReportActionItemParentActionProps = {
/** The transaction thread report associated with the current report, if any */
transactionThreadReport: OnyxEntry;
- /** Array of report actions for this report */
- reportActions: OnyxTypes.ReportAction[];
-
/** Report actions belonging to the report's parent */
parentReportAction: OnyxEntry;
@@ -55,7 +52,6 @@ type ReportActionItemParentActionProps = {
function ReportActionItemParentAction({
report,
transactionThreadReport,
- reportActions,
parentReportAction,
index = 0,
shouldHideThreadDividerLine = false,
@@ -144,7 +140,6 @@ function ReportActionItemParentAction({
}
parentReportAction={parentReportAction}
report={ancestor.report}
- reportActions={reportActions}
transactionThreadReport={transactionThreadReport}
action={ancestor.reportAction}
displayAsGroup={false}
diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx
index 6924725eb99b..3cdb7b59ae7a 100644
--- a/src/pages/home/report/ReportActionsList.tsx
+++ b/src/pages/home/report/ReportActionsList.tsx
@@ -176,6 +176,7 @@ function ReportActionsList({
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`);
const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID});
const participantsContext = useContext(PersonalDetailsContext);
+ const [scrollOffset, setScrollOffset] = useState(0);
useEffect(() => {
const unsubscriber = Visibility.onVisibilityChange(() => {
@@ -219,6 +220,7 @@ function ReportActionsList({
}, [report.reportID]);
const prevUnreadMarkerReportActionID = useRef(null);
+
/**
* Whether a message is NOT from the active user and it was received while the user was offline.
*/
@@ -512,6 +514,7 @@ function ReportActionsList({
};
const trackVerticalScrolling = (event: NativeSyntheticEvent) => {
+ setScrollOffset(event.nativeEvent.contentOffset.y);
scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y;
handleUnreadFloatingButton();
onScroll?.(event);
@@ -618,7 +621,6 @@ function ReportActionsList({
({item: reportAction, index}: ListRenderItemInfo) => (
{
loadOlderChats(false);
+
+ requestAnimationFrame(() => {
+ reportScrollManager.ref?.current?.scrollToOffset({
+ offset: scrollingVerticalOffset.current - scrollOffset,
+ animated: false,
+ });
+ });
+ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [loadOlderChats]);
// Parse Fullstory attributes on initial render
diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx
index 1a0d8db13661..b9cc7406b890 100644
--- a/src/pages/home/report/ReportActionsListItemRenderer.tsx
+++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx
@@ -11,9 +11,6 @@ type ReportActionsListItemRendererProps = {
/** All the data of the action item */
reportAction: ReportAction;
- /** Array of report actions for the report */
- reportActions: ReportAction[];
-
/** The report's parentReportAction */
parentReportAction: OnyxEntry;
@@ -56,7 +53,6 @@ type ReportActionsListItemRendererProps = {
function ReportActionsListItemRenderer({
reportAction,
- reportActions = [],
parentReportAction,
index,
report,
@@ -152,7 +148,6 @@ function ReportActionsListItemRenderer({
parentReportAction={parentReportAction}
reportID={report.reportID}
report={report}
- reportActions={reportActions}
transactionThreadReport={transactionThreadReport}
index={index}
isFirstVisibleReportAction={isFirstVisibleReportAction}
@@ -169,7 +164,6 @@ function ReportActionsListItemRenderer({
transactionThreadReport={transactionThreadReport}
parentReportActionForTransactionThread={parentReportActionForTransactionThread}
action={action}
- reportActions={reportActions}
linkedReportActionID={linkedReportActionID}
displayAsGroup={displayAsGroup}
shouldDisplayNewMarker={shouldDisplayNewMarker}
diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx
index 88b4cae332ed..6bd98e47f8aa 100755
--- a/src/pages/home/report/ReportActionsView.tsx
+++ b/src/pages/home/report/ReportActionsView.tsx
@@ -5,6 +5,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import EmptyStateComponent from '@components/EmptyStateComponent';
import * as Illustrations from '@components/Icon/Illustrations';
+import LoadingBar from '@components/LoadingBar';
import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView';
import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton';
import useCopySelectionHelper from '@hooks/useCopySelectionHelper';
@@ -53,8 +54,8 @@ type ReportActionsViewProps = {
/** The report's parentReportAction */
parentReportAction: OnyxEntry;
- /** The report metadata loading states */
- isLoadingInitialReportActions?: boolean;
+ /** The report's metadata */
+ reportMetadata: OnyxTypes.ReportMetadata;
/** The reportID of the transaction thread report associated with this current report, if any */
// eslint-disable-next-line react/no-unused-prop-types
@@ -73,7 +74,7 @@ function ReportActionsView({
report,
parentReportAction,
reportActions: allReportActions,
- isLoadingInitialReportActions,
+ reportMetadata,
transactionThreadReportID,
hasNewerActions,
hasOlderActions,
@@ -103,6 +104,8 @@ function ReportActionsView({
const reportID = report.reportID;
const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]);
+ const {isLoadingInitialReportActions, isLoadingOlderReportActions, isLoadingNewerReportActions, hasLoadingNewerReportActionsError} = reportMetadata;
+
useEffect(() => {
// When we linked to message - we do not need to wait for initial actions - they already exists
if (!reportActionID || !isOffline) {
@@ -211,6 +214,8 @@ function ReportActionsView({
[reportActions, isOffline, canPerformWriteAction],
);
+ const showLoadingBar = !!isLoadingOlderReportActions || !!isLoadingNewerReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions);
+
const reportActionIDMap = useMemo(() => {
const reportActionIDs = allReportActions?.map((action) => action.reportActionID);
return reportActions.map((action) => ({
@@ -282,7 +287,7 @@ function ReportActionsView({
isOffline ||
// If there was an error only try again once on initial mount. We should also still load
// more in case we have cached messages.
- didLoadNewerChats.current ||
+ !!(didLoadNewerChats.current && hasLoadingNewerReportActionsError) ||
newestReportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)
) {
return;
@@ -303,7 +308,7 @@ function ReportActionsView({
getNewerActions(reportID, newestReportAction.reportActionID);
}
},
- [reportActionID, isFocused, newestReportAction, hasNewerActions, isOffline, transactionThreadReport, reportActionIDMap, reportID],
+ [reportActionID, isFocused, newestReportAction, hasNewerActions, isOffline, hasLoadingNewerReportActionsError, transactionThreadReport, reportActionIDMap, reportID],
);
/**
@@ -372,6 +377,7 @@ function ReportActionsView({
const shouldEnableAutoScroll = (hasNewestReportAction && (!reportActionID || !isNavigatingToLinkedMessage)) || (transactionThreadReport && !prevTransactionThreadReport);
return (
<>
+
progressBarWrapper: {
height: 2,
- width: '100%',
- backgroundColor: theme.transparent,
overflow: 'hidden',
- position: 'absolute',
- bottom: -1,
- },
-
- progressBar: {
- height: '100%',
- backgroundColor: theme.success,
- width: '100%',
},
accountSwitcherAnchorPosition: {
diff --git a/src/styles/theme/themes/dark.ts b/src/styles/theme/themes/dark.ts
index 4d5894812195..8a9fd3cbd804 100644
--- a/src/styles/theme/themes/dark.ts
+++ b/src/styles/theme/themes/dark.ts
@@ -85,8 +85,8 @@ const darkTheme = {
tooltipSupportingText: colors.productLight800,
tooltipPrimaryText: colors.productLight900,
trialBannerBackgroundColor: colors.green700,
- skeletonLHNIn: colors.productDark400,
- skeletonLHNOut: colors.productDark600,
+ skeletonLHNIn: colors.productDark200,
+ skeletonLHNOut: colors.productDark300,
QRLogo: colors.green400,
starDefaultBG: 'rgb(254, 228, 94)',
mapAttributionText: colors.black,
diff --git a/src/styles/theme/themes/light.ts b/src/styles/theme/themes/light.ts
index 1a187e85b0a2..6f4e5ee30e9d 100644
--- a/src/styles/theme/themes/light.ts
+++ b/src/styles/theme/themes/light.ts
@@ -85,8 +85,8 @@ const lightTheme = {
tooltipSupportingText: colors.productDark800,
tooltipPrimaryText: colors.productDark900,
trialBannerBackgroundColor: colors.green100,
- skeletonLHNIn: colors.productLight400,
- skeletonLHNOut: colors.productLight600,
+ skeletonLHNIn: colors.productLight200,
+ skeletonLHNOut: colors.productLight300,
QRLogo: colors.green400,
starDefaultBG: 'rgb(254, 228, 94)',
mapAttributionText: colors.black,
diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx
index 73df63153b1d..e08a4d50f51f 100644
--- a/tests/ui/PaginationTest.tsx
+++ b/tests/ui/PaginationTest.tsx
@@ -359,10 +359,10 @@ describe('Pagination', () => {
TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 3);
TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
- TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 1);
+ TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 2);
// We now have 10 messages. 5 from the initial OpenReport and 5 from the GetNewerActions call.
- expect(getReportActions()).toHaveLength(10);
+ expect(getReportActions()).toHaveLength(15);
// Simulate the backend returning no new messages to simulate reaching the start of the chat.
mockGetNewerActions(0);
@@ -374,9 +374,9 @@ describe('Pagination', () => {
TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 3);
TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
- TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 1);
+ TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 3);
// We still have 15 messages. 5 from the initial OpenReport and 5 from the GetNewerActions call.
- expect(getReportActions()).toHaveLength(10);
+ expect(getReportActions()).toHaveLength(15);
});
});