From d913d8aef3553246f18343919e178d6400d71bfb Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 26 Feb 2025 08:41:42 +0100 Subject: [PATCH 01/29] Loading state perception improvement --- src/components/LoadingBar.tsx | 102 +++++++------------- src/components/MoneyReportHeader.tsx | 3 - src/components/MoneyRequestHeader.tsx | 3 - src/pages/home/HeaderView.tsx | 2 - src/pages/home/ReportScreen.tsx | 1 + src/pages/home/report/ReportActionsView.tsx | 8 ++ 6 files changed, 45 insertions(+), 74 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index e6d1ec0cd66d..0d1740fcfeb6 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,83 +1,53 @@ import React, {useEffect} from 'react'; -import Animated, {cancelAnimation, Easing, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated'; -import useThemeStyles from '@hooks/useThemeStyles'; -import CONST from '@src/CONST'; +import {View} from 'react-native'; +import Animated, {runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import colors from '@styles/theme/colors'; -type LoadingBarProps = { - // Whether or not to show the loading bar - shouldShow: boolean; -}; +type LoadingBarProps = {shouldShow: boolean}; function LoadingBar({shouldShow}: LoadingBarProps) { - const left = useSharedValue(0); - const width = useSharedValue(0); + const left = useSharedValue(-30); const opacity = useSharedValue(0); - const styles = useThemeStyles(); + const isAnimating = useSharedValue(false); 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})); - - 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, - ), - ), - ); - } else { - opacity.set( - withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { - cancelAnimation(left); - cancelAnimation(width); - }), - ); + if (shouldShow && !isAnimating.value) { + isAnimating.value = true; + opacity.value = withTiming(1, {duration: 300}); + + left.value = withTiming(100, {duration: 1200}, () => { + runOnJS(() => { + if (shouldShow) { + left.value = -30; + left.value = withTiming(100, {duration: 1200}); + } else { + opacity.value = withTiming(0, {duration: 300}, () => { + isAnimating.value = false; + }); + } + })(); + }); + } else if (!shouldShow) { + opacity.value = withTiming(0, {duration: 300}, () => { + isAnimating.value = 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(() => ({ - left: `${left.get()}%`, - width: `${width.get()}%`, - })); - - const animatedContainerStyle = useAnimatedStyle(() => ({ - opacity: opacity.get(), + const barStyle = useAnimatedStyle(() => ({ + left: `${left.value}%`, + width: '30%', + height: '100%', + backgroundColor: colors.green, + opacity: opacity.value, + borderRadius: 2, })); return ( - - - + + + ); } -LoadingBar.displayName = 'ProgressBar'; - export default LoadingBar; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 1d6c3fe4cfba..ef6c6be9608b 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -72,7 +72,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'; @@ -209,7 +208,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; @@ -493,7 +491,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea )} )} - {isHoldMenuVisible && requestType !== undefined && ( )} - ); } diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 73b1a4af8ec1..c7e8cb957c5d 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -106,7 +106,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID) ?? getNonEmptyStringOnyxID(report?.reportID)}`); const policy = usePolicy(report?.policyID); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL); const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL); const [account] = useOnyx(ONYXKEYS.ACCOUNT); @@ -384,7 +383,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, )} - {shouldShowDiscount && isChatUsedForOnboarding && } diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7627e77610fd..38bfa3f3c021 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -734,6 +734,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { report={report} reportActions={reportActions} isLoadingInitialReportActions={reportMetadata?.isLoadingInitialReportActions} + isLoadingOlderReportActions={reportMetadata?.isLoadingOlderReportActions} hasNewerActions={hasNewerActions} hasOlderActions={hasOlderActions} parentReportAction={parentReportAction} diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 1cdaccccdbe9..7a5f04c07cfe 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'; @@ -56,6 +57,9 @@ type ReportActionsViewProps = { /** The report metadata loading states */ isLoadingInitialReportActions?: boolean; + /** Are we loading more report actions? */ + isLoadingOlderReportActions?: boolean; + /** The reportID of the transaction thread report associated with this current report, if any */ // eslint-disable-next-line react/no-unused-prop-types transactionThreadReportID?: string | null; @@ -74,6 +78,7 @@ function ReportActionsView({ parentReportAction, reportActions: allReportActions, isLoadingInitialReportActions, + isLoadingOlderReportActions = true, transactionThreadReportID, hasNewerActions, hasOlderActions, @@ -211,6 +216,8 @@ function ReportActionsView({ [reportActions, isOffline, canPerformWriteAction], ); + const showLoadingBar = isLoadingOlderReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); + const reportActionIDMap = useMemo(() => { const reportActionIDs = allReportActions?.map((action) => action.reportActionID); return reportActions.map((action) => ({ @@ -373,6 +380,7 @@ function ReportActionsView({ const shouldEnableAutoScroll = (hasNewestReportAction && (!reportActionID || !isNavigatingToLinkedMessage)) || (transactionThreadReport && !prevTransactionThreadReport); return ( <> + Date: Fri, 28 Feb 2025 10:23:39 +0100 Subject: [PATCH 02/29] Loading state perception improvement - loading bar and loading older actions logic --- src/components/LoadingBar.tsx | 4 ++-- src/pages/home/report/PureReportActionItem.tsx | 5 ----- .../home/report/ReportActionItemParentAction.tsx | 5 ----- src/pages/home/report/ReportActionsList.tsx | 11 +++++++++-- .../home/report/ReportActionsListItemRenderer.tsx | 6 ------ 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 0d1740fcfeb6..0fe64ce27834 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; -import Animated, {runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {runOnUI, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import colors from '@styles/theme/colors'; type LoadingBarProps = {shouldShow: boolean}; @@ -16,7 +16,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { opacity.value = withTiming(1, {duration: 300}); left.value = withTiming(100, {duration: 1200}, () => { - runOnJS(() => { + runOnUI(() => { if (shouldShow) { left.value = -30; left.value = withTiming(100, {duration: 1200}); diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 49a855fc690b..d43bc0ef5de2 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -148,10 +148,6 @@ type PureReportActionItemProps = { /** The transaction thread report associated with the report for this action, if any */ transactionThreadReport?: OnyxEntry; - /** 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; @@ -1245,7 +1241,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 332f773a7b44..38cfb0c5fd84 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, @@ -140,7 +136,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 a37a28a7ee0a..11b01df80636 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -174,6 +174,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(() => { @@ -501,6 +502,7 @@ function ReportActionsList({ }; const trackVerticalScrolling = (event: NativeSyntheticEvent) => { + setScrollOffset(event.nativeEvent.contentOffset.y); scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y; handleUnreadFloatingButton(); onScroll?.(event); @@ -607,7 +609,6 @@ function ReportActionsList({ ({item: reportAction, index}: ListRenderItemInfo) => ( { loadOlderChats(false); + + requestAnimationFrame(() => { + reportScrollManager.ref?.current?.scrollToOffset({ + offset: scrollingVerticalOffset.current - scrollOffset, + animated: false, + }); + }); }, [loadOlderChats]); // Parse Fullstory attributes on initial render diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx index 1e00133ca40b..0247bc0d9fea 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.tsx +++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx @@ -12,9 +12,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; @@ -57,7 +54,6 @@ type ReportActionsListItemRendererProps = { function ReportActionsListItemRenderer({ reportAction, - reportActions = [], parentReportAction, index, report, @@ -158,7 +154,6 @@ function ReportActionsListItemRenderer({ parentReportAction={parentReportAction} reportID={report.reportID} report={report} - reportActions={reportActions} transactionThreadReport={transactionThreadReport} index={index} isFirstVisibleReportAction={isFirstVisibleReportAction} @@ -175,7 +170,6 @@ function ReportActionsListItemRenderer({ transactionThreadReport={transactionThreadReport} parentReportActionForTransactionThread={parentReportActionForTransactionThread} action={action} - reportActions={reportActions} linkedReportActionID={linkedReportActionID} displayAsGroup={displayAsGroup} shouldDisplayNewMarker={shouldDisplayNewMarker} From fcaead7022325261ce62ece896ef135e093866bf Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Fri, 28 Feb 2025 10:54:20 +0100 Subject: [PATCH 03/29] Loading state perception improvement: less blinking skeletons --- .../ReportActionsSkeletonView/SkeletonViewLines.tsx | 1 + src/styles/theme/themes/dark.ts | 4 ++-- src/styles/theme/themes/light.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) 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} > Date: Fri, 28 Feb 2025 11:56:26 +0100 Subject: [PATCH 04/29] remove redundant reportActions props --- src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx | 1 - src/pages/Debug/ReportAction/DebugReportActionPreview.tsx | 1 - src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx | 1 - 3 files changed, 3 deletions(-) diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index 1162146b8f4d..adf7a78c6a2a 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -107,7 +107,6 @@ function DebugReportActionCreatePage({ Date: Fri, 28 Feb 2025 12:58:23 +0100 Subject: [PATCH 05/29] remove redundant imports --- src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx | 6 +++--- src/pages/home/HeaderView.tsx | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index a6ddbd9a0a72..2738edadbd6a 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -3,7 +3,7 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import ReportActionItem from '@pages/home/report/ReportActionItem'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction} from '@src/types/onyx'; @@ -20,7 +20,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/non-nullable-type-assertion-style const action = Object.values(reportActions ?? {})?.find((reportAction) => { - const IOUTransactionID = ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID : -1; + const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : -1; return IOUTransactionID === props.transaction?.transactionID; }); @@ -33,7 +33,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { Date: Fri, 28 Feb 2025 13:23:29 +0100 Subject: [PATCH 06/29] loading bar animation performance improvement --- src/components/LoadingBar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 0fe64ce27834..7f8e4de9c224 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; -import Animated, {runOnUI, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import colors from '@styles/theme/colors'; type LoadingBarProps = {shouldShow: boolean}; @@ -16,7 +16,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { opacity.value = withTiming(1, {duration: 300}); left.value = withTiming(100, {duration: 1200}, () => { - runOnUI(() => { + requestAnimationFrame(() => { if (shouldShow) { left.value = -30; left.value = withTiming(100, {duration: 1200}); @@ -25,7 +25,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { isAnimating.value = false; }); } - })(); + }); }); } else if (!shouldShow) { opacity.value = withTiming(0, {duration: 300}, () => { From 44ef83154bd3a9b6ec10a605efdb2ac6138fa77b Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Fri, 28 Feb 2025 13:50:26 +0100 Subject: [PATCH 07/29] manage scrolling position only for web and desktop --- src/pages/home/report/ReportActionsList.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index c8469a52a9e6..d893e21d500f 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -60,6 +60,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; +import getPlatform from '@libs/getPlatform'; import FloatingMessageCounter from './FloatingMessageCounter'; import getInitialNumToRender from './getInitialNumReportActionsToRender'; import ListBoundaryLoader from './ListBoundaryLoader'; @@ -226,6 +227,11 @@ function ReportActionsList({ }, [report.reportID]); const prevUnreadMarkerReportActionID = useRef(null); + + const platform = getPlatform(); + const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP; + + /** * Whether a message is NOT from the active user and it was received while the user was offline. */ @@ -719,12 +725,14 @@ function ReportActionsList({ const onEndReached = useCallback(() => { loadOlderChats(false); - requestAnimationFrame(() => { - reportScrollManager.ref?.current?.scrollToOffset({ - offset: scrollingVerticalOffset.current - scrollOffset, - animated: false, + if (isWebOrDesktop) { + requestAnimationFrame(() => { + reportScrollManager.ref?.current?.scrollToOffset({ + offset: scrollingVerticalOffset.current - scrollOffset, + animated: false, + }); }); - }); + } }, [loadOlderChats]); // Parse Fullstory attributes on initial render From a4231000e6e766a8c48d6db8646238a3e2266760 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Fri, 28 Feb 2025 14:53:41 +0100 Subject: [PATCH 08/29] fix linter errors --- src/components/LoadingBar.tsx | 30 +++++++++---------- .../DebugReportActionCreatePage.tsx | 8 ++--- .../DuplicateTransactionItem.tsx | 5 ++-- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 7f8e4de9c224..f8ac88fbc228 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -11,35 +11,35 @@ function LoadingBar({shouldShow}: LoadingBarProps) { const isAnimating = useSharedValue(false); useEffect(() => { - if (shouldShow && !isAnimating.value) { - isAnimating.value = true; - opacity.value = withTiming(1, {duration: 300}); + if (shouldShow && !isAnimating.get()) { + isAnimating.set(true); + opacity.set(withTiming(1, {duration: 300})); - left.value = withTiming(100, {duration: 1200}, () => { + left.set(withTiming(100, {duration: 1200}, () => { requestAnimationFrame(() => { if (shouldShow) { - left.value = -30; - left.value = withTiming(100, {duration: 1200}); + left.set(-30); + left.set(withTiming(100, {duration: 1200})); } else { - opacity.value = withTiming(0, {duration: 300}, () => { - isAnimating.value = false; - }); + opacity.set(withTiming(0, {duration: 300}, () => { + isAnimating.set(false); + })); } }); - }); + })); } else if (!shouldShow) { - opacity.value = withTiming(0, {duration: 300}, () => { - isAnimating.value = false; - }); + opacity.set(withTiming(0, {duration: 300}, () => { + isAnimating.set(false); + })); } }, [shouldShow]); const barStyle = useAnimatedStyle(() => ({ - left: `${left.value}%`, + left: `${left.get()}%`, width: '30%', height: '100%', backgroundColor: colors.green, - opacity: opacity.value, + opacity: opacity.get(), borderRadius: 2, })); diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index adf7a78c6a2a..b7823842d4b8 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -12,11 +12,9 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DebugUtils from '@libs/DebugUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; -import * as NumberUtils from '@libs/NumberUtils'; import ReportActionItem from '@pages/home/report/ReportActionItem'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -25,6 +23,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList, ReportAction, Session} from '@src/types/onyx'; +import {rand64} from '@libs/NumberUtils'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; type DebugReportActionCreatePageProps = PlatformStackScreenProps; @@ -32,7 +32,7 @@ const getInitialReportAction = (reportID: string, session: OnyxEntry, 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}) => ( diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index 2738edadbd6a..5fe10a4378ef 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -7,6 +7,7 @@ import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/R import ReportActionItem from '@pages/home/report/ReportActionItem'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction} from '@src/types/onyx'; +import CONST from '@src/CONST'; type DuplicateTransactionItemProps = { transaction: OnyxEntry; @@ -20,7 +21,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/non-nullable-type-assertion-style const action = Object.values(reportActions ?? {})?.find((reportAction) => { - const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : -1; + const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : CONST.DEFAULT_NUMBER_ID; return IOUTransactionID === props.transaction?.transactionID; }); @@ -33,7 +34,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { Date: Fri, 28 Feb 2025 15:00:01 +0100 Subject: [PATCH 09/29] fix linter errors --- src/components/LoadingBar.tsx | 37 +++++++++++-------- .../DebugReportActionCreatePage.tsx | 4 +- .../DuplicateTransactionItem.tsx | 2 +- src/pages/home/report/ReportActionsList.tsx | 4 +- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index f8ac88fbc228..17b88d08ba84 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -15,23 +15,30 @@ function LoadingBar({shouldShow}: LoadingBarProps) { isAnimating.set(true); opacity.set(withTiming(1, {duration: 300})); - left.set(withTiming(100, {duration: 1200}, () => { - requestAnimationFrame(() => { - if (shouldShow) { - left.set(-30); - left.set(withTiming(100, {duration: 1200})); - } else { - opacity.set(withTiming(0, {duration: 300}, () => { - isAnimating.set(false); - })); - } - }); - })); + left.set( + withTiming(100, {duration: 1200}, () => { + requestAnimationFrame(() => { + if (shouldShow) { + left.set(-30); + left.set(withTiming(100, {duration: 1200})); + } else { + opacity.set( + withTiming(0, {duration: 300}, () => { + isAnimating.set(false); + }), + ); + } + }); + }), + ); } else if (!shouldShow) { - opacity.set(withTiming(0, {duration: 300}, () => { - isAnimating.set(false); - })); + opacity.set( + withTiming(0, {duration: 300}, () => { + isAnimating.set(false); + }), + ); } + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [shouldShow]); const barStyle = useAnimatedStyle(() => ({ diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index b7823842d4b8..2331d84b1a16 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -12,9 +12,11 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DebugUtils from '@libs/DebugUtils'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; +import {rand64} from '@libs/NumberUtils'; import ReportActionItem from '@pages/home/report/ReportActionItem'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -23,8 +25,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList, ReportAction, Session} from '@src/types/onyx'; -import {rand64} from '@libs/NumberUtils'; -import {canUseTouchScreen} from '@libs/DeviceCapabilities'; type DebugReportActionCreatePageProps = PlatformStackScreenProps; diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index 5fe10a4378ef..eb036c6fb3b3 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -5,9 +5,9 @@ import {useOnyx} from 'react-native-onyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import ReportActionItem from '@pages/home/report/ReportActionItem'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction} from '@src/types/onyx'; -import CONST from '@src/CONST'; type DuplicateTransactionItemProps = { transaction: OnyxEntry; diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index d893e21d500f..2cc3df2af3c1 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -21,6 +21,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import {isSafari} from '@libs/Browser'; import DateUtils from '@libs/DateUtils'; import {getChatFSAttributes, parseFSAttributes} from '@libs/Fullstory'; +import getPlatform from '@libs/getPlatform'; import isReportTopmostSplitNavigator from '@libs/Navigation/helpers/isReportTopmostSplitNavigator'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation from '@libs/Navigation/Navigation'; @@ -60,7 +61,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -import getPlatform from '@libs/getPlatform'; import FloatingMessageCounter from './FloatingMessageCounter'; import getInitialNumToRender from './getInitialNumReportActionsToRender'; import ListBoundaryLoader from './ListBoundaryLoader'; @@ -231,7 +231,6 @@ function ReportActionsList({ const platform = getPlatform(); const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP; - /** * Whether a message is NOT from the active user and it was received while the user was offline. */ @@ -733,6 +732,7 @@ function ReportActionsList({ }); }); } + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [loadOlderChats]); // Parse Fullstory attributes on initial render From 3cc105bc052d4863f271812498e70c4c2f0202db Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 4 Mar 2025 18:08:48 +0100 Subject: [PATCH 10/29] Addressed PR comments --- src/components/LoadingBar.tsx | 9 +++++++-- .../DuplicateTransactionItem.tsx | 2 +- src/pages/home/report/ReportActionsList.tsx | 16 +++++----------- src/styles/index.ts | 9 --------- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 17b88d08ba84..6109f1123d8a 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -2,13 +2,18 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import colors from '@styles/theme/colors'; +import useThemeStyles from '@hooks/useThemeStyles'; -type LoadingBarProps = {shouldShow: boolean}; +type LoadingBarProps = { + // Whether to show the loading bar + shouldShow: boolean; +}; function LoadingBar({shouldShow}: LoadingBarProps) { const left = useSharedValue(-30); const opacity = useSharedValue(0); const isAnimating = useSharedValue(false); + const styles = useThemeStyles(); useEffect(() => { if (shouldShow && !isAnimating.get()) { @@ -51,7 +56,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { })); return ( - + ); diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index eb036c6fb3b3..ec180136a979 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -34,7 +34,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { (null); - const platform = getPlatform(); - const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP; - /** * Whether a message is NOT from the active user and it was received while the user was offline. */ @@ -715,14 +711,12 @@ function ReportActionsList({ const onEndReached = useCallback(() => { loadOlderChats(false); - if (isWebOrDesktop) { - requestAnimationFrame(() => { - reportScrollManager.ref?.current?.scrollToOffset({ - offset: scrollingVerticalOffset.current - scrollOffset, - animated: 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]); diff --git a/src/styles/index.ts b/src/styles/index.ts index 1c808291f98a..79b8833ead9e 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5412,16 +5412,7 @@ const styles = (theme: ThemeColors) => progressBarWrapper: { height: 2, - width: '100%', - backgroundColor: theme.transparent, overflow: 'hidden', - marginBottom: -1, - }, - - progressBar: { - height: '100%', - backgroundColor: theme.success, - width: '100%', }, accountSwitcherAnchorPosition: { From a0de6d53cd1c7e40302068aafe0ada1cdfe9b298 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 4 Mar 2025 18:50:07 +0100 Subject: [PATCH 11/29] Fix prettier error & default isLoadingOlderReportActions flag --- src/components/LoadingBar.tsx | 2 +- src/pages/home/report/ReportActionsView.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 6109f1123d8a..932c8b1d7728 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,8 +1,8 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import colors from '@styles/theme/colors'; import useThemeStyles from '@hooks/useThemeStyles'; +import colors from '@styles/theme/colors'; type LoadingBarProps = { // Whether to show the loading bar diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 400e5bf3085d..95c03bde4011 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -78,7 +78,7 @@ function ReportActionsView({ parentReportAction, reportActions: allReportActions, isLoadingInitialReportActions, - isLoadingOlderReportActions = true, + isLoadingOlderReportActions, transactionThreadReportID, hasNewerActions, hasOlderActions, From 4315668466f36d9d74033e26011559806c08bff2 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 4 Mar 2025 19:00:05 +0100 Subject: [PATCH 12/29] fix linter errors --- src/pages/home/report/ReportActionsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 95c03bde4011..fab8aef65405 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -216,7 +216,7 @@ function ReportActionsView({ [reportActions, isOffline, canPerformWriteAction], ); - const showLoadingBar = isLoadingOlderReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); + const showLoadingBar = !!isLoadingOlderReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); const reportActionIDMap = useMemo(() => { const reportActionIDs = allReportActions?.map((action) => action.reportActionID); From 821bd06b897c2b941194f9678e5dca91e45315b0 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 4 Mar 2025 19:09:30 +0100 Subject: [PATCH 13/29] show loading bar when fetching newer actions --- src/pages/home/ReportScreen.tsx | 3 +-- src/pages/home/report/ReportActionsView.tsx | 14 ++++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7d17f2692a22..99906a5551d8 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -731,8 +731,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ; - /** The report metadata loading states */ - isLoadingInitialReportActions?: boolean; - - /** Are we loading more report actions? */ - isLoadingOlderReportActions?: 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 @@ -77,8 +74,7 @@ function ReportActionsView({ report, parentReportAction, reportActions: allReportActions, - isLoadingInitialReportActions, - isLoadingOlderReportActions, + reportMetadata, transactionThreadReportID, hasNewerActions, hasOlderActions, @@ -108,6 +104,8 @@ function ReportActionsView({ const reportID = report.reportID; const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); + const {isLoadingInitialReportActions, isLoadingOlderReportActions, isLoadingNewerReportActions} = reportMetadata; + useEffect(() => { // When we linked to message - we do not need to wait for initial actions - they already exists if (!reportActionID || !isOffline) { @@ -216,7 +214,7 @@ function ReportActionsView({ [reportActions, isOffline, canPerformWriteAction], ); - const showLoadingBar = !!isLoadingOlderReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); + const showLoadingBar = !!isLoadingOlderReportActions || !!isLoadingNewerReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); const reportActionIDMap = useMemo(() => { const reportActionIDs = allReportActions?.map((action) => action.reportActionID); From ce8867870e19c6edab6b366e051a32c4d9fdbe3d Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 26 Feb 2025 08:41:42 +0100 Subject: [PATCH 14/29] Loading state perception improvement --- src/components/LoadingBar.tsx | 102 +++++++------------- src/components/MoneyReportHeader.tsx | 3 - src/components/MoneyRequestHeader.tsx | 3 - src/pages/home/HeaderView.tsx | 2 - src/pages/home/ReportScreen.tsx | 1 + src/pages/home/report/ReportActionsView.tsx | 8 ++ 6 files changed, 45 insertions(+), 74 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index e6d1ec0cd66d..0d1740fcfeb6 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,83 +1,53 @@ import React, {useEffect} from 'react'; -import Animated, {cancelAnimation, Easing, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated'; -import useThemeStyles from '@hooks/useThemeStyles'; -import CONST from '@src/CONST'; +import {View} from 'react-native'; +import Animated, {runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import colors from '@styles/theme/colors'; -type LoadingBarProps = { - // Whether or not to show the loading bar - shouldShow: boolean; -}; +type LoadingBarProps = {shouldShow: boolean}; function LoadingBar({shouldShow}: LoadingBarProps) { - const left = useSharedValue(0); - const width = useSharedValue(0); + const left = useSharedValue(-30); const opacity = useSharedValue(0); - const styles = useThemeStyles(); + const isAnimating = useSharedValue(false); 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})); - - 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, - ), - ), - ); - } else { - opacity.set( - withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { - cancelAnimation(left); - cancelAnimation(width); - }), - ); + if (shouldShow && !isAnimating.value) { + isAnimating.value = true; + opacity.value = withTiming(1, {duration: 300}); + + left.value = withTiming(100, {duration: 1200}, () => { + runOnJS(() => { + if (shouldShow) { + left.value = -30; + left.value = withTiming(100, {duration: 1200}); + } else { + opacity.value = withTiming(0, {duration: 300}, () => { + isAnimating.value = false; + }); + } + })(); + }); + } else if (!shouldShow) { + opacity.value = withTiming(0, {duration: 300}, () => { + isAnimating.value = 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(() => ({ - left: `${left.get()}%`, - width: `${width.get()}%`, - })); - - const animatedContainerStyle = useAnimatedStyle(() => ({ - opacity: opacity.get(), + const barStyle = useAnimatedStyle(() => ({ + left: `${left.value}%`, + width: '30%', + height: '100%', + backgroundColor: colors.green, + opacity: opacity.value, + borderRadius: 2, })); return ( - - - + + + ); } -LoadingBar.displayName = 'ProgressBar'; - 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/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 763ee3b10039..7018074eae86 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -106,7 +106,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID) ?? getNonEmptyStringOnyxID(report?.reportID)}`); const policy = usePolicy(report?.policyID); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL); const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL); const [account] = useOnyx(ONYXKEYS.ACCOUNT); @@ -389,7 +388,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, )} - {shouldShowEarlyDiscountBanner && ( 0 && isLoadingInitialReportActions); + const reportActionIDMap = useMemo(() => { const reportActionIDs = allReportActions?.map((action) => action.reportActionID); return reportActions.map((action) => ({ @@ -372,6 +379,7 @@ function ReportActionsView({ const shouldEnableAutoScroll = (hasNewestReportAction && (!reportActionID || !isNavigatingToLinkedMessage)) || (transactionThreadReport && !prevTransactionThreadReport); return ( <> + Date: Fri, 28 Feb 2025 10:23:39 +0100 Subject: [PATCH 15/29] Loading state perception improvement - loading bar and loading older actions logic --- src/components/LoadingBar.tsx | 4 ++-- src/pages/home/report/PureReportActionItem.tsx | 5 ----- .../home/report/ReportActionItemParentAction.tsx | 5 ----- src/pages/home/report/ReportActionsList.tsx | 11 +++++++++-- .../home/report/ReportActionsListItemRenderer.tsx | 6 ------ 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 0d1740fcfeb6..0fe64ce27834 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; -import Animated, {runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {runOnUI, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import colors from '@styles/theme/colors'; type LoadingBarProps = {shouldShow: boolean}; @@ -16,7 +16,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { opacity.value = withTiming(1, {duration: 300}); left.value = withTiming(100, {duration: 1200}, () => { - runOnJS(() => { + runOnUI(() => { if (shouldShow) { left.value = -30; left.value = withTiming(100, {duration: 1200}); diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 8ef1fe4a9442..5ba9aa1a18ea 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -163,10 +163,6 @@ type PureReportActionItemProps = { /** The transaction thread report associated with the report for this action, if any */ transactionThreadReport?: OnyxEntry; - /** 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; @@ -1296,7 +1292,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..972a7818d8f9 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(() => { @@ -512,6 +513,7 @@ function ReportActionsList({ }; const trackVerticalScrolling = (event: NativeSyntheticEvent) => { + setScrollOffset(event.nativeEvent.contentOffset.y); scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y; handleUnreadFloatingButton(); onScroll?.(event); @@ -618,7 +620,6 @@ function ReportActionsList({ ({item: reportAction, index}: ListRenderItemInfo) => ( { loadOlderChats(false); + + requestAnimationFrame(() => { + reportScrollManager.ref?.current?.scrollToOffset({ + offset: scrollingVerticalOffset.current - scrollOffset, + animated: false, + }); + }); }, [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} From 4ee407705dfdcaaef2b80d1054da51ac3969091f Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Fri, 28 Feb 2025 10:54:20 +0100 Subject: [PATCH 16/29] Loading state perception improvement: less blinking skeletons --- .../ReportActionsSkeletonView/SkeletonViewLines.tsx | 1 + src/styles/theme/themes/dark.ts | 4 ++-- src/styles/theme/themes/light.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) 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} > Date: Fri, 28 Feb 2025 11:56:26 +0100 Subject: [PATCH 17/29] remove redundant reportActions props --- src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx | 1 - src/pages/Debug/ReportAction/DebugReportActionPreview.tsx | 1 - src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx | 1 - 3 files changed, 3 deletions(-) diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index 1162146b8f4d..adf7a78c6a2a 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -107,7 +107,6 @@ function DebugReportActionCreatePage({ Date: Fri, 28 Feb 2025 12:58:23 +0100 Subject: [PATCH 18/29] remove redundant imports --- src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx | 6 +++--- src/pages/home/HeaderView.tsx | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index a6ddbd9a0a72..2738edadbd6a 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -3,7 +3,7 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import ReportActionItem from '@pages/home/report/ReportActionItem'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction} from '@src/types/onyx'; @@ -20,7 +20,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/non-nullable-type-assertion-style const action = Object.values(reportActions ?? {})?.find((reportAction) => { - const IOUTransactionID = ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID : -1; + const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : -1; return IOUTransactionID === props.transaction?.transactionID; }); @@ -33,7 +33,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { Date: Fri, 28 Feb 2025 13:23:29 +0100 Subject: [PATCH 19/29] loading bar animation performance improvement --- src/components/LoadingBar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 0fe64ce27834..7f8e4de9c224 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; -import Animated, {runOnUI, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import colors from '@styles/theme/colors'; type LoadingBarProps = {shouldShow: boolean}; @@ -16,7 +16,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { opacity.value = withTiming(1, {duration: 300}); left.value = withTiming(100, {duration: 1200}, () => { - runOnUI(() => { + requestAnimationFrame(() => { if (shouldShow) { left.value = -30; left.value = withTiming(100, {duration: 1200}); @@ -25,7 +25,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { isAnimating.value = false; }); } - })(); + }); }); } else if (!shouldShow) { opacity.value = withTiming(0, {duration: 300}, () => { From 21d7f288568d929a6943a2317c7897a57cdce54b Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Fri, 28 Feb 2025 13:50:26 +0100 Subject: [PATCH 20/29] manage scrolling position only for web and desktop --- src/pages/home/report/ReportActionsList.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 972a7818d8f9..99385d3cb9d8 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -61,6 +61,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; +import getPlatform from '@libs/getPlatform'; import FloatingMessageCounter from './FloatingMessageCounter'; import getInitialNumToRender from './getInitialNumReportActionsToRender'; import ListBoundaryLoader from './ListBoundaryLoader'; @@ -220,6 +221,11 @@ function ReportActionsList({ }, [report.reportID]); const prevUnreadMarkerReportActionID = useRef(null); + + const platform = getPlatform(); + const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP; + + /** * Whether a message is NOT from the active user and it was received while the user was offline. */ @@ -710,12 +716,14 @@ function ReportActionsList({ const onEndReached = useCallback(() => { loadOlderChats(false); - requestAnimationFrame(() => { - reportScrollManager.ref?.current?.scrollToOffset({ - offset: scrollingVerticalOffset.current - scrollOffset, - animated: false, + if (isWebOrDesktop) { + requestAnimationFrame(() => { + reportScrollManager.ref?.current?.scrollToOffset({ + offset: scrollingVerticalOffset.current - scrollOffset, + animated: false, + }); }); - }); + } }, [loadOlderChats]); // Parse Fullstory attributes on initial render From 8f5878341638aaa4598b41936d7075321aaf27e1 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Fri, 28 Feb 2025 14:53:41 +0100 Subject: [PATCH 21/29] fix linter errors --- src/components/LoadingBar.tsx | 30 +++++++++---------- .../DebugReportActionCreatePage.tsx | 8 ++--- .../DuplicateTransactionItem.tsx | 5 ++-- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 7f8e4de9c224..f8ac88fbc228 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -11,35 +11,35 @@ function LoadingBar({shouldShow}: LoadingBarProps) { const isAnimating = useSharedValue(false); useEffect(() => { - if (shouldShow && !isAnimating.value) { - isAnimating.value = true; - opacity.value = withTiming(1, {duration: 300}); + if (shouldShow && !isAnimating.get()) { + isAnimating.set(true); + opacity.set(withTiming(1, {duration: 300})); - left.value = withTiming(100, {duration: 1200}, () => { + left.set(withTiming(100, {duration: 1200}, () => { requestAnimationFrame(() => { if (shouldShow) { - left.value = -30; - left.value = withTiming(100, {duration: 1200}); + left.set(-30); + left.set(withTiming(100, {duration: 1200})); } else { - opacity.value = withTiming(0, {duration: 300}, () => { - isAnimating.value = false; - }); + opacity.set(withTiming(0, {duration: 300}, () => { + isAnimating.set(false); + })); } }); - }); + })); } else if (!shouldShow) { - opacity.value = withTiming(0, {duration: 300}, () => { - isAnimating.value = false; - }); + opacity.set(withTiming(0, {duration: 300}, () => { + isAnimating.set(false); + })); } }, [shouldShow]); const barStyle = useAnimatedStyle(() => ({ - left: `${left.value}%`, + left: `${left.get()}%`, width: '30%', height: '100%', backgroundColor: colors.green, - opacity: opacity.value, + opacity: opacity.get(), borderRadius: 2, })); diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index adf7a78c6a2a..b7823842d4b8 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -12,11 +12,9 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DebugUtils from '@libs/DebugUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; -import * as NumberUtils from '@libs/NumberUtils'; import ReportActionItem from '@pages/home/report/ReportActionItem'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -25,6 +23,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList, ReportAction, Session} from '@src/types/onyx'; +import {rand64} from '@libs/NumberUtils'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; type DebugReportActionCreatePageProps = PlatformStackScreenProps; @@ -32,7 +32,7 @@ const getInitialReportAction = (reportID: string, session: OnyxEntry, 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}) => ( diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index 2738edadbd6a..5fe10a4378ef 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -7,6 +7,7 @@ import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/R import ReportActionItem from '@pages/home/report/ReportActionItem'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction} from '@src/types/onyx'; +import CONST from '@src/CONST'; type DuplicateTransactionItemProps = { transaction: OnyxEntry; @@ -20,7 +21,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/non-nullable-type-assertion-style const action = Object.values(reportActions ?? {})?.find((reportAction) => { - const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : -1; + const IOUTransactionID = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : CONST.DEFAULT_NUMBER_ID; return IOUTransactionID === props.transaction?.transactionID; }); @@ -33,7 +34,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { Date: Fri, 28 Feb 2025 15:00:01 +0100 Subject: [PATCH 22/29] fix linter errors --- src/components/LoadingBar.tsx | 37 +++++++++++-------- .../DebugReportActionCreatePage.tsx | 4 +- .../DuplicateTransactionItem.tsx | 2 +- src/pages/home/report/ReportActionsList.tsx | 4 +- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index f8ac88fbc228..17b88d08ba84 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -15,23 +15,30 @@ function LoadingBar({shouldShow}: LoadingBarProps) { isAnimating.set(true); opacity.set(withTiming(1, {duration: 300})); - left.set(withTiming(100, {duration: 1200}, () => { - requestAnimationFrame(() => { - if (shouldShow) { - left.set(-30); - left.set(withTiming(100, {duration: 1200})); - } else { - opacity.set(withTiming(0, {duration: 300}, () => { - isAnimating.set(false); - })); - } - }); - })); + left.set( + withTiming(100, {duration: 1200}, () => { + requestAnimationFrame(() => { + if (shouldShow) { + left.set(-30); + left.set(withTiming(100, {duration: 1200})); + } else { + opacity.set( + withTiming(0, {duration: 300}, () => { + isAnimating.set(false); + }), + ); + } + }); + }), + ); } else if (!shouldShow) { - opacity.set(withTiming(0, {duration: 300}, () => { - isAnimating.set(false); - })); + opacity.set( + withTiming(0, {duration: 300}, () => { + isAnimating.set(false); + }), + ); } + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [shouldShow]); const barStyle = useAnimatedStyle(() => ({ diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index b7823842d4b8..2331d84b1a16 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -12,9 +12,11 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DebugUtils from '@libs/DebugUtils'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; +import {rand64} from '@libs/NumberUtils'; import ReportActionItem from '@pages/home/report/ReportActionItem'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -23,8 +25,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList, ReportAction, Session} from '@src/types/onyx'; -import {rand64} from '@libs/NumberUtils'; -import {canUseTouchScreen} from '@libs/DeviceCapabilities'; type DebugReportActionCreatePageProps = PlatformStackScreenProps; diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index 5fe10a4378ef..eb036c6fb3b3 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -5,9 +5,9 @@ import {useOnyx} from 'react-native-onyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import ReportActionItem from '@pages/home/report/ReportActionItem'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction} from '@src/types/onyx'; -import CONST from '@src/CONST'; type DuplicateTransactionItemProps = { transaction: OnyxEntry; diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 99385d3cb9d8..fc066e67a264 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -21,6 +21,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import {isSafari} from '@libs/Browser'; import DateUtils from '@libs/DateUtils'; import {getChatFSAttributes, parseFSAttributes} from '@libs/Fullstory'; +import getPlatform from '@libs/getPlatform'; import isReportTopmostSplitNavigator from '@libs/Navigation/helpers/isReportTopmostSplitNavigator'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation from '@libs/Navigation/Navigation'; @@ -61,7 +62,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -import getPlatform from '@libs/getPlatform'; import FloatingMessageCounter from './FloatingMessageCounter'; import getInitialNumToRender from './getInitialNumReportActionsToRender'; import ListBoundaryLoader from './ListBoundaryLoader'; @@ -225,7 +225,6 @@ function ReportActionsList({ const platform = getPlatform(); const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP; - /** * Whether a message is NOT from the active user and it was received while the user was offline. */ @@ -724,6 +723,7 @@ function ReportActionsList({ }); }); } + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [loadOlderChats]); // Parse Fullstory attributes on initial render From ec54ce54e314be95c9fb1891512706e31af4e178 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 4 Mar 2025 18:08:48 +0100 Subject: [PATCH 23/29] Addressed PR comments --- src/components/LoadingBar.tsx | 9 +++++++-- .../DuplicateTransactionItem.tsx | 2 +- src/pages/home/report/ReportActionsList.tsx | 16 +++++----------- src/styles/index.ts | 9 --------- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 17b88d08ba84..6109f1123d8a 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -2,13 +2,18 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import colors from '@styles/theme/colors'; +import useThemeStyles from '@hooks/useThemeStyles'; -type LoadingBarProps = {shouldShow: boolean}; +type LoadingBarProps = { + // Whether to show the loading bar + shouldShow: boolean; +}; function LoadingBar({shouldShow}: LoadingBarProps) { const left = useSharedValue(-30); const opacity = useSharedValue(0); const isAnimating = useSharedValue(false); + const styles = useThemeStyles(); useEffect(() => { if (shouldShow && !isAnimating.get()) { @@ -51,7 +56,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { })); return ( - + ); diff --git a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx index eb036c6fb3b3..ec180136a979 100644 --- a/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx +++ b/src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx @@ -34,7 +34,7 @@ function DuplicateTransactionItem(props: DuplicateTransactionItemProps) { (null); - const platform = getPlatform(); - const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP; - /** * Whether a message is NOT from the active user and it was received while the user was offline. */ @@ -715,14 +711,12 @@ function ReportActionsList({ const onEndReached = useCallback(() => { loadOlderChats(false); - if (isWebOrDesktop) { - requestAnimationFrame(() => { - reportScrollManager.ref?.current?.scrollToOffset({ - offset: scrollingVerticalOffset.current - scrollOffset, - animated: 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]); diff --git a/src/styles/index.ts b/src/styles/index.ts index 1c808291f98a..79b8833ead9e 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5412,16 +5412,7 @@ const styles = (theme: ThemeColors) => progressBarWrapper: { height: 2, - width: '100%', - backgroundColor: theme.transparent, overflow: 'hidden', - marginBottom: -1, - }, - - progressBar: { - height: '100%', - backgroundColor: theme.success, - width: '100%', }, accountSwitcherAnchorPosition: { From 17e18ac14f9daa4b106e64260594d94c77a0a93d Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 4 Mar 2025 18:50:07 +0100 Subject: [PATCH 24/29] Fix prettier error & default isLoadingOlderReportActions flag --- src/components/LoadingBar.tsx | 2 +- src/pages/home/report/ReportActionsView.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 6109f1123d8a..932c8b1d7728 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,8 +1,8 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import colors from '@styles/theme/colors'; import useThemeStyles from '@hooks/useThemeStyles'; +import colors from '@styles/theme/colors'; type LoadingBarProps = { // Whether to show the loading bar diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 400e5bf3085d..95c03bde4011 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -78,7 +78,7 @@ function ReportActionsView({ parentReportAction, reportActions: allReportActions, isLoadingInitialReportActions, - isLoadingOlderReportActions = true, + isLoadingOlderReportActions, transactionThreadReportID, hasNewerActions, hasOlderActions, From d4a73a6cbda96a633377dccdf1875145f5f72c1f Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 4 Mar 2025 19:00:05 +0100 Subject: [PATCH 25/29] fix linter errors --- src/pages/home/report/ReportActionsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 95c03bde4011..fab8aef65405 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -216,7 +216,7 @@ function ReportActionsView({ [reportActions, isOffline, canPerformWriteAction], ); - const showLoadingBar = isLoadingOlderReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); + const showLoadingBar = !!isLoadingOlderReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); const reportActionIDMap = useMemo(() => { const reportActionIDs = allReportActions?.map((action) => action.reportActionID); From d7c6e08f7311d674d67b5b04897517e82bde2798 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 4 Mar 2025 19:09:30 +0100 Subject: [PATCH 26/29] show loading bar when fetching newer actions --- src/pages/home/ReportScreen.tsx | 3 +-- src/pages/home/report/ReportActionsView.tsx | 14 ++++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7d17f2692a22..99906a5551d8 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -731,8 +731,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ; - /** The report metadata loading states */ - isLoadingInitialReportActions?: boolean; - - /** Are we loading more report actions? */ - isLoadingOlderReportActions?: 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 @@ -77,8 +74,7 @@ function ReportActionsView({ report, parentReportAction, reportActions: allReportActions, - isLoadingInitialReportActions, - isLoadingOlderReportActions, + reportMetadata, transactionThreadReportID, hasNewerActions, hasOlderActions, @@ -108,6 +104,8 @@ function ReportActionsView({ const reportID = report.reportID; const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); + const {isLoadingInitialReportActions, isLoadingOlderReportActions, isLoadingNewerReportActions} = reportMetadata; + useEffect(() => { // When we linked to message - we do not need to wait for initial actions - they already exists if (!reportActionID || !isOffline) { @@ -216,7 +214,7 @@ function ReportActionsView({ [reportActions, isOffline, canPerformWriteAction], ); - const showLoadingBar = !!isLoadingOlderReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); + const showLoadingBar = !!isLoadingOlderReportActions || !!isLoadingNewerReportActions || !!(visibleReportActions.length > 0 && isLoadingInitialReportActions); const reportActionIDMap = useMemo(() => { const reportActionIDs = allReportActions?.map((action) => action.reportActionID); From 2590b10dc51a93ade064290ad1f4c47a0436a2e1 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 5 Mar 2025 13:49:59 +0100 Subject: [PATCH 27/29] load newer actions fix --- src/pages/home/report/ReportActionsView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index d4f2d9a7437c..6bd98e47f8aa 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -104,7 +104,7 @@ function ReportActionsView({ const reportID = report.reportID; const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); - const {isLoadingInitialReportActions, isLoadingOlderReportActions, isLoadingNewerReportActions} = reportMetadata; + const {isLoadingInitialReportActions, isLoadingOlderReportActions, isLoadingNewerReportActions, hasLoadingNewerReportActionsError} = reportMetadata; useEffect(() => { // When we linked to message - we do not need to wait for initial actions - they already exists @@ -287,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; @@ -308,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], ); /** From 322dcd1c8e07185ee056efb8325ea92f0a26b4f0 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 5 Mar 2025 14:03:59 +0100 Subject: [PATCH 28/29] load newer actions fix --- tests/ui/PaginationTest.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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); }); }); From 863a88dc0c7fd5eba412697e00223b91a218efb9 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 10 Mar 2025 12:09:42 +0100 Subject: [PATCH 29/29] Addressed comments --- src/CONST.ts | 2 ++ src/components/LoadingBar.tsx | 13 ++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) 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 932c8b1d7728..e13702517e10 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -3,6 +3,7 @@ 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 to show the loading bar @@ -18,17 +19,17 @@ function LoadingBar({shouldShow}: LoadingBarProps) { useEffect(() => { if (shouldShow && !isAnimating.get()) { isAnimating.set(true); - opacity.set(withTiming(1, {duration: 300})); + opacity.set(withTiming(1, {duration: CONST.TIMING.SKELETON_FADE_DURATION})); left.set( - withTiming(100, {duration: 1200}, () => { + withTiming(100, {duration: CONST.TIMING.SKELETON_SLIDE_DURATION}, () => { requestAnimationFrame(() => { if (shouldShow) { left.set(-30); - left.set(withTiming(100, {duration: 1200})); + left.set(withTiming(100, {duration: CONST.TIMING.SKELETON_SLIDE_DURATION})); } else { opacity.set( - withTiming(0, {duration: 300}, () => { + withTiming(0, {duration: CONST.TIMING.SKELETON_FADE_DURATION}, () => { isAnimating.set(false); }), ); @@ -38,7 +39,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { ); } else if (!shouldShow) { opacity.set( - withTiming(0, {duration: 300}, () => { + withTiming(0, {duration: CONST.TIMING.SKELETON_FADE_DURATION}, () => { isAnimating.set(false); }), ); @@ -62,4 +63,6 @@ function LoadingBar({shouldShow}: LoadingBarProps) { ); } +LoadingBar.displayName = 'LoadingBar'; + export default LoadingBar;