From 601bec3bed96868a14b7e4394bc08c6e6cd31c38 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 13 Mar 2025 22:09:07 +0100 Subject: [PATCH] Revert "Merge pull request #57610 from callstack-internal/loading-state-perception-improvement" This reverts commit 52666fba347cb148119c10ebc9d2b38161674863, reversing changes made to c25a843cef4067bb3b697937affbaabf8ff22881. --- src/CONST.ts | 2 - src/components/LoadingBar.tsx | 85 +++++++++++-------- src/components/MoneyReportHeader.tsx | 3 + src/components/MoneyRequestHeader.tsx | 3 + .../SkeletonViewLines.tsx | 1 - .../DebugReportActionCreatePage.tsx | 9 +- .../ReportAction/DebugReportActionPreview.tsx | 1 + .../DuplicateTransactionItem.tsx | 8 +- src/pages/home/HeaderView.tsx | 3 + src/pages/home/ReportScreen.tsx | 2 +- .../home/report/PureReportActionItem.tsx | 5 ++ .../report/ReportActionItemParentAction.tsx | 5 ++ src/pages/home/report/ReportActionsList.tsx | 3 +- .../report/ReportActionsListItemRenderer.tsx | 6 ++ src/pages/home/report/ReportActionsView.tsx | 16 ++-- src/styles/index.ts | 10 +++ tests/ui/PaginationTest.tsx | 8 +- 17 files changed, 107 insertions(+), 63 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index a8d2fffd661f..b73bc7c33846 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1494,8 +1494,6 @@ 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 e13702517e10..e6d1ec0cd66d 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,68 +1,83 @@ import React, {useEffect} from 'react'; -import {View} from 'react-native'; -import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {cancelAnimation, Easing, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated'; import useThemeStyles from '@hooks/useThemeStyles'; -import colors from '@styles/theme/colors'; import CONST from '@src/CONST'; type LoadingBarProps = { - // Whether to show the loading bar + // Whether or not to show the loading bar shouldShow: boolean; }; function LoadingBar({shouldShow}: LoadingBarProps) { - const left = useSharedValue(-30); + const left = useSharedValue(0); + const width = useSharedValue(0); const opacity = useSharedValue(0); - const isAnimating = useSharedValue(false); const styles = useThemeStyles(); useEffect(() => { - if (shouldShow && !isAnimating.get()) { - isAnimating.set(true); - opacity.set(withTiming(1, {duration: CONST.TIMING.SKELETON_FADE_DURATION})); + 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})); left.set( - 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); - }), - ); - } - }); - }), + 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, + ), + ), ); - } else if (!shouldShow) { + } else { opacity.set( - withTiming(0, {duration: CONST.TIMING.SKELETON_FADE_DURATION}, () => { - isAnimating.set(false); + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { + 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 barStyle = useAnimatedStyle(() => ({ + const animatedIndicatorStyle = useAnimatedStyle(() => ({ left: `${left.get()}%`, - width: '30%', - height: '100%', - backgroundColor: colors.green, + width: `${width.get()}%`, + })); + + const animatedContainerStyle = useAnimatedStyle(() => ({ opacity: opacity.get(), - borderRadius: 2, })); return ( - - - + + + ); } -LoadingBar.displayName = 'LoadingBar'; +LoadingBar.displayName = 'ProgressBar'; export default LoadingBar; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 06d679d98207..a085385589a3 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -77,6 +77,7 @@ 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'; @@ -234,6 +235,7 @@ 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; @@ -528,6 +530,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea )} )} + {isHoldMenuVisible && requestType !== undefined && ( )} + ); } diff --git a/src/components/ReportActionsSkeletonView/SkeletonViewLines.tsx b/src/components/ReportActionsSkeletonView/SkeletonViewLines.tsx index d3ea26ab5880..c4c2a3f43eb3 100644 --- a/src/components/ReportActionsSkeletonView/SkeletonViewLines.tsx +++ b/src/components/ReportActionsSkeletonView/SkeletonViewLines.tsx @@ -20,7 +20,6 @@ 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: rand64(), + reportActionID: NumberUtils.rand64(), created: DateUtils.getDBTime(), actorAccountID: session?.accountID, avatar: (session?.accountID && personalDetailsList?.[session.accountID]?.avatar) ?? '', @@ -78,7 +78,7 @@ function DebugReportActionCreatePage({ {({safeAreaPaddingBottomStyle}) => ( @@ -107,6 +107,7 @@ function DebugReportActionCreatePage({ { - const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : CONST.DEFAULT_NUMBER_ID; + const IOUTransactionID = ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID : -1; return IOUTransactionID === props.transaction?.transactionID; }); @@ -34,8 +33,9 @@ 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; @@ -1298,6 +1302,7 @@ 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 5d3284d7bee8..b7134fd7059e 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -36,6 +36,9 @@ 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; @@ -52,6 +55,7 @@ type ReportActionItemParentActionProps = { function ReportActionItemParentAction({ report, transactionThreadReport, + reportActions, parentReportAction, index = 0, shouldHideThreadDividerLine = false, @@ -140,6 +144,7 @@ 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 511748f9194b..5c140589e4f3 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -221,7 +221,6 @@ 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. */ @@ -622,6 +621,7 @@ function ReportActionsList({ ({item: reportAction, index}: ListRenderItemInfo) => ( ; @@ -53,6 +56,7 @@ type ReportActionsListItemRendererProps = { function ReportActionsListItemRenderer({ reportAction, + reportActions = [], parentReportAction, index, report, @@ -148,6 +152,7 @@ function ReportActionsListItemRenderer({ parentReportAction={parentReportAction} reportID={report.reportID} report={report} + reportActions={reportActions} transactionThreadReport={transactionThreadReport} index={index} isFirstVisibleReportAction={isFirstVisibleReportAction} @@ -164,6 +169,7 @@ 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 6bd98e47f8aa..88b4cae332ed 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -5,7 +5,6 @@ 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'; @@ -54,8 +53,8 @@ type ReportActionsViewProps = { /** The report's parentReportAction */ parentReportAction: OnyxEntry; - /** The report's metadata */ - reportMetadata: OnyxTypes.ReportMetadata; + /** The report metadata loading states */ + isLoadingInitialReportActions?: boolean; /** The reportID of the transaction thread report associated with this current report, if any */ // eslint-disable-next-line react/no-unused-prop-types @@ -74,7 +73,7 @@ function ReportActionsView({ report, parentReportAction, reportActions: allReportActions, - reportMetadata, + isLoadingInitialReportActions, transactionThreadReportID, hasNewerActions, hasOlderActions, @@ -104,8 +103,6 @@ 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) { @@ -214,8 +211,6 @@ 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) => ({ @@ -287,7 +282,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 && hasLoadingNewerReportActionsError) || + didLoadNewerChats.current || newestReportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) ) { return; @@ -308,7 +303,7 @@ function ReportActionsView({ getNewerActions(reportID, newestReportAction.reportActionID); } }, - [reportActionID, isFocused, newestReportAction, hasNewerActions, isOffline, hasLoadingNewerReportActionsError, transactionThreadReport, reportActionIDMap, reportID], + [reportActionID, isFocused, newestReportAction, hasNewerActions, isOffline, transactionThreadReport, reportActionIDMap, reportID], ); /** @@ -377,7 +372,6 @@ 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/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx index e08a4d50f51f..73df63153b1d 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', 2); + TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 1); // We now have 10 messages. 5 from the initial OpenReport and 5 from the GetNewerActions call. - expect(getReportActions()).toHaveLength(15); + expect(getReportActions()).toHaveLength(10); // 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', 3); + TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 1); // We still have 15 messages. 5 from the initial OpenReport and 5 from the GetNewerActions call. - expect(getReportActions()).toHaveLength(15); + expect(getReportActions()).toHaveLength(10); }); });