diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 732b53c26244..4b9da05c7202 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1506,6 +1506,7 @@ const CONST = { PUSHER_PING_PONG: 'pusher_ping_pong', LOCATION_UPDATE_INTERVAL: 5000, PLAY_SOUND_MESSAGE_DEBOUNCE_TIME: 500, + NOTIFY_NEW_ACTION_DELAY: 700, SKELETON_ANIMATION_SPEED: 3, SEARCH_OPTIONS_COMPARISON: 'search_options_comparison', SEARCH_MOST_RECENT_OPTIONS: 'search_most_recent_options', diff --git a/src/components/Search/SearchList.tsx b/src/components/Search/SearchList.tsx index b2019f79b17e..8bc0ccb52e76 100644 --- a/src/components/Search/SearchList.tsx +++ b/src/components/Search/SearchList.tsx @@ -29,6 +29,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import {isMobileChrome} from '@libs/Browser'; import {addKeyDownPressListener, removeKeyDownPressListener} from '@libs/KeyboardShortcut/KeyDownPressListener'; +import durationHighlightItem from '@libs/Navigation/helpers/getDurationHighlightItem'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -287,16 +288,9 @@ function SearchList( clearTimeout(itemFocusTimeoutRef.current); } - const duration = - CONST.ANIMATED_HIGHLIGHT_ENTRY_DELAY + - CONST.ANIMATED_HIGHLIGHT_ENTRY_DURATION + - CONST.ANIMATED_HIGHLIGHT_START_DELAY + - CONST.ANIMATED_HIGHLIGHT_START_DURATION + - CONST.ANIMATED_HIGHLIGHT_END_DELAY + - CONST.ANIMATED_HIGHLIGHT_END_DURATION; itemFocusTimeoutRef.current = setTimeout(() => { setItemsToHighlight(null); - }, duration); + }, durationHighlightItem); }, [data, scrollToIndex], ); diff --git a/src/libs/Navigation/helpers/getDurationHighlightItem.ts b/src/libs/Navigation/helpers/getDurationHighlightItem.ts new file mode 100644 index 000000000000..12af591b4658 --- /dev/null +++ b/src/libs/Navigation/helpers/getDurationHighlightItem.ts @@ -0,0 +1,10 @@ +import CONST from '@src/CONST'; + +const durationHighlightItem = + CONST.ANIMATED_HIGHLIGHT_ENTRY_DELAY + + CONST.ANIMATED_HIGHLIGHT_ENTRY_DURATION + + CONST.ANIMATED_HIGHLIGHT_START_DELAY + + CONST.ANIMATED_HIGHLIGHT_START_DURATION + + CONST.ANIMATED_HIGHLIGHT_END_DELAY + + CONST.ANIMATED_HIGHLIGHT_END_DURATION; +export default durationHighlightItem; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 2cf7c3150e79..17d761e40d9f 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5609,7 +5609,11 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { } if (activeReportID && !isMoneyRequestReport) { - notifyNewAction(activeReportID, payeeAccountID); + Navigation.setNavigationActionToMicrotaskQueue(() => + setTimeout(() => { + notifyNewAction(activeReportID, payeeAccountID, reportPreviewAction); + }, CONST.TIMING.NOTIFY_NEW_ACTION_DELAY), + ); } } diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index dc6f16e1dd82..52f74959df94 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -362,6 +362,9 @@ type PureReportActionItemProps = { /** Whether to show border for MoneyRequestReportPreviewContent */ shouldShowBorder?: boolean; + /** Whether to highlight the action for a few seconds */ + shouldHighlight?: boolean; + /** Current user's account id */ currentUserAccountID?: number; }; @@ -428,6 +431,7 @@ function PureReportActionItem({ dismissTrackExpenseActionableWhisper = () => {}, userBillingFundID, shouldShowBorder, + shouldHighlight = false, currentUserAccountID, }: PureReportActionItemProps) { const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); @@ -457,8 +461,9 @@ function PureReportActionItem({ const isReportArchived = useReportIsArchived(report?.reportID); const highlightedBackgroundColorIfNeeded = useMemo( - () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), - [StyleUtils, isReportActionLinked, theme.messageHighlightBG], + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + () => (isReportActionLinked || shouldHighlight ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), + [StyleUtils, isReportActionLinked, theme.messageHighlightBG, shouldHighlight], ); const reportPreviewStyles = StyleUtils.getMoneyRequestReportPreviewStyle(shouldUseNarrowLayout, 1, undefined, undefined); @@ -1646,6 +1651,7 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { deepEqual(prevProps.missingPaymentMethod, nextProps.missingPaymentMethod) && prevProps.reimbursementDeQueuedOrCanceledActionMessage === nextProps.reimbursementDeQueuedOrCanceledActionMessage && prevProps.modifiedExpenseMessage === nextProps.modifiedExpenseMessage && + prevProps.shouldHighlight === nextProps.shouldHighlight && prevProps.userBillingFundID === nextProps.userBillingFundID ); }); diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 08f84c743279..c1fde839c0b3 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -22,6 +22,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import {isSafari} from '@libs/Browser'; import DateUtils from '@libs/DateUtils'; import {getChatFSAttributes, parseFSAttributes} from '@libs/Fullstory'; +import durationHighlightItem from '@libs/Navigation/helpers/getDurationHighlightItem'; import isReportTopmostSplitNavigator from '@libs/Navigation/helpers/isReportTopmostSplitNavigator'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation from '@libs/Navigation/Navigation'; @@ -173,6 +174,7 @@ function ReportActionsList({ const isReportArchived = useReportIsArchived(report?.reportID); const [isScrollToBottomEnabled, setIsScrollToBottomEnabled] = useState(false); + const [actionIdToHighlight, setActionIdToHighlight] = useState(''); useEffect(() => { const unsubscribe = Visibility.onVisibilityChange(() => { @@ -409,7 +411,7 @@ function ReportActionsList({ }, [lastAction, prevSortedVisibleReportActionsObjects, reportScrollManager]); const scrollToBottomForCurrentUserAction = useCallback( - (isFromCurrentUser: boolean) => { + (isFromCurrentUser: boolean, action?: OnyxTypes.ReportAction) => { InteractionManager.runAfterInteractions(() => { // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where // they are now in the list. @@ -425,14 +427,42 @@ function ReportActionsList({ }); return; } + const index = sortedVisibleReportActions.findIndex((item) => keyExtractor(item) === action?.reportActionID); + if (action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { + if (index > 0) { + setTimeout(() => { + reportScrollManager.scrollToIndex(index); + }, 100); + } else { + setIsFloatingMessageCounterVisible(false); + reportScrollManager.scrollToBottom(); + } + if (action?.reportActionID) { + setActionIdToHighlight(action.reportActionID); + } + } else { + setIsFloatingMessageCounterVisible(false); + reportScrollManager.scrollToBottom(); + } - setIsFloatingMessageCounterVisible(false); - reportScrollManager.scrollToBottom(); setIsScrollToBottomEnabled(true); }); }, - [report.reportID, reportScrollManager, setIsFloatingMessageCounterVisible], + [report.reportID, reportScrollManager, setIsFloatingMessageCounterVisible, sortedVisibleReportActions, reportScrollManager], ); + + // Clear the highlighted report action after scrolling and highlighting + useEffect(() => { + if (actionIdToHighlight === '') { + return; + } + // Time highlight is the same as SearchPage + const timer = setTimeout(() => { + setActionIdToHighlight(''); + }, durationHighlightItem); + return () => clearTimeout(timer); + }, [actionIdToHighlight]); + useEffect(() => { // Why are we doing this, when in the cleanup of the useEffect we are already calling the unsubscribe function? // Answer: On web, when navigating to another report screen, the previous report screen doesn't get unmounted, @@ -608,6 +638,7 @@ function ReportActionsList({ isFirstVisibleReportAction={firstVisibleReportActionID === reportAction.reportActionID} shouldUseThreadDividerLine={shouldUseThreadDividerLine} transactions={Object.values(transactions ?? {})} + shouldHighlight={actionIdToHighlight === reportAction.reportActionID} /> ); }, @@ -627,6 +658,7 @@ function ReportActionsList({ shouldUseThreadDividerLine, firstVisibleReportActionID, unreadMarkerReportActionID, + actionIdToHighlight, ], ); diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx index 513904335f67..710e4482a91f 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.tsx +++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx @@ -61,6 +61,9 @@ type ReportActionsListItemRendererProps = { /** If the thread divider line will be used */ shouldUseThreadDividerLine?: boolean; + + /** Animate highlight action in few seconds */ + shouldHighlight?: boolean; }; function ReportActionsListItemRenderer({ @@ -81,6 +84,7 @@ function ReportActionsListItemRenderer({ shouldDisplayReplyDivider, isFirstVisibleReportAction = false, shouldUseThreadDividerLine = false, + shouldHighlight = false, parentReportActionForTransactionThread, }: ReportActionsListItemRendererProps) { const originalMessage = useMemo(() => getOriginalMessage(reportAction), [reportAction]); @@ -204,6 +208,7 @@ function ReportActionsListItemRenderer({ index={index} isFirstVisibleReportAction={isFirstVisibleReportAction} shouldUseThreadDividerLine={shouldUseThreadDividerLine} + shouldHighlight={shouldHighlight} /> ); } diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 8cf87fd84688..40667f344cee 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -44,6 +44,7 @@ import {WRITE_COMMANDS} from '@libs/API/types'; import type {ApiCommand} from '@libs/API/types'; import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; import {translateLocal} from '@libs/Localize'; +import Navigation from '@libs/Navigation/Navigation'; import {rand64} from '@libs/NumberUtils'; import {getLoginsByAccountIDs} from '@libs/PersonalDetailsUtils'; import { @@ -1613,7 +1614,7 @@ describe('actions/IOU', () => { comment: '', }, }); - expect(notifyNewAction).toHaveBeenCalledTimes(1); + expect(Navigation.setNavigationActionToMicrotaskQueue).toHaveBeenCalledTimes(1); }); });