diff --git a/src/CONST.ts b/src/CONST.ts index 55f0dafd8517..7fd13fb66799 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -12,6 +12,7 @@ import type {Video} from './libs/actions/Report'; import type {MileageRate} from './libs/DistanceRequestUtils'; import BankAccount from './libs/models/BankAccount'; import {addTrailingForwardSlash} from './libs/Url'; +import ONYXKEYS from './ONYXKEYS'; import SCREENS from './SCREENS'; import type PlaidBankAccount from './types/onyx/PlaidBankAccount'; @@ -6271,6 +6272,15 @@ const CONST = { BEFORE: 'Before', AFTER: 'After', }, + SNAPSHOT_ONYX_KEYS: [ + ONYXKEYS.COLLECTION.REPORT, + ONYXKEYS.COLLECTION.POLICY, + ONYXKEYS.COLLECTION.TRANSACTION, + ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + ONYXKEYS.COLLECTION.REPORT_ACTIONS, + ONYXKEYS.PERSONAL_DETAILS_LIST, + ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, + ], }, REFERRER: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b98717b51f5d..c1175997648d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -357,14 +357,18 @@ const ROUTES = { isAuthTokenRequired?: boolean, fileName?: string, attachmentLink?: string, + hashKey?: number, ) => { const reportParam = reportID ? `&reportID=${reportID}` : ''; const accountParam = accountID ? `&accountID=${accountID}` : ''; const authTokenParam = isAuthTokenRequired ? '&isAuthTokenRequired=true' : ''; const fileNameParam = fileName ? `&fileName=${fileName}` : ''; const attachmentLinkParam = attachmentLink ? `&attachmentLink=${attachmentLink}` : ''; + const hashKeyParam = hashKey ? `&hashKey=${hashKey}` : ''; - return `attachment?source=${encodeURIComponent(url)}&type=${type as string}${reportParam}${accountParam}${authTokenParam}${fileNameParam}${attachmentLinkParam}` as const; + return `attachment?source=${encodeURIComponent(url)}&type=${ + type as string + }${reportParam}${accountParam}${authTokenParam}${fileNameParam}${attachmentLinkParam}${hashKeyParam}` as const; }, }, REPORT_PARTICIPANTS: { diff --git a/src/components/AddPaymentMethodMenu.tsx b/src/components/AddPaymentMethodMenu.tsx index 50020906075d..34da724665fe 100644 --- a/src/components/AddPaymentMethodMenu.tsx +++ b/src/components/AddPaymentMethodMenu.tsx @@ -2,8 +2,8 @@ import type {RefObject} from 'react'; import React, {useEffect, useState} from 'react'; import type {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import {completePaymentOnboarding} from '@libs/actions/IOU'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; diff --git a/src/components/AttachmentContext.ts b/src/components/AttachmentContext.ts index 4ed6bdc9084f..0c3308ae75e5 100644 --- a/src/components/AttachmentContext.ts +++ b/src/components/AttachmentContext.ts @@ -6,12 +6,14 @@ type AttachmentContextProps = { type?: ValueOf; reportID?: string; accountID?: number; + hashKey?: number; }; const AttachmentContext = createContext({ type: undefined, reportID: undefined, accountID: undefined, + hashKey: undefined, }); AttachmentContext.displayName = 'AttachmentContext'; diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 50caaac3dd81..bc33e9cc6167 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -5,13 +5,13 @@ import type {ListRenderItemInfo} from 'react-native'; import {Keyboard, PixelRatio, View} from 'react-native'; import type {ComposedGesture, GestureType} from 'react-native-gesture-handler'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; -import {useOnyx} from 'react-native-onyx'; import Animated, {scrollTo, useAnimatedRef, useSharedValue} from 'react-native-reanimated'; import type {Attachment, AttachmentSource} from '@components/Attachments/types'; import BlockingView from '@components/BlockingViews/BlockingView'; import * as Illustrations from '@components/Icon/Illustrations'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index f6d6ba447af3..73a413a3d924 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -2,7 +2,6 @@ import {Str} from 'expensify-common'; import React, {memo, useContext, useEffect, useState} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import type {Attachment, AttachmentSource} from '@components/Attachments/types'; import DistanceEReceipt from '@components/DistanceEReceipt'; @@ -15,6 +14,8 @@ import Text from '@components/Text'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useOnyx from '@hooks/useOnyx'; +import useSearchState from '@hooks/useSearchState'; import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -121,6 +122,7 @@ function AttachmentView({ const [hasPDFFailedToLoad, setHasPDFFailedToLoad] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file?.name && Str.isVideo(file.name)); const isUsedInCarousel = !!attachmentCarouselPagerContext?.pagerRef; + const {isOnSearch} = useSearchState(); useEffect(() => { if (!isFocused && !(file && isUsedInAttachmentModal)) { @@ -297,7 +299,7 @@ function AttachmentView({ return ( diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index e9b3b555385d..663ceddd4011 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -1,8 +1,8 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import useOnyx from '@hooks/useOnyx'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/EReceiptThumbnail.tsx b/src/components/EReceiptThumbnail.tsx index 4b0c0caa1035..2d6a6bc04ecd 100644 --- a/src/components/EReceiptThumbnail.tsx +++ b/src/components/EReceiptThumbnail.tsx @@ -1,6 +1,6 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; +import useOnyx from '@hooks/useOnyx'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {getTransactionDetails} from '@libs/ReportUtils'; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx index ad7ea87f4c9b..0cde2123ceb2 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx @@ -32,7 +32,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) { {({report}) => ( - {({accountID, type}) => ( + {({accountID, type, hashKey}) => ( diff --git a/src/components/KYCWall/BaseKYCWall.tsx b/src/components/KYCWall/BaseKYCWall.tsx index 307acae4d5d3..68e43f43538e 100644 --- a/src/components/KYCWall/BaseKYCWall.tsx +++ b/src/components/KYCWall/BaseKYCWall.tsx @@ -2,8 +2,8 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Dimensions} from 'react-native'; import type {EmitterSubscription, GestureResponderEvent, View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; +import useOnyx from '@hooks/useOnyx'; import {openPersonalBankAccountSetupView} from '@libs/actions/BankAccounts'; import {completePaymentOnboarding} from '@libs/actions/IOU'; import getClickedTargetLocation from '@libs/getClickedTargetLocation'; diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index ac202c1dc6a0..36ccc8d19c8c 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -3,11 +3,11 @@ import type {MapState} from '@rnmapbox/maps'; import Mapbox, {MarkerView, setAccessToken} from '@rnmapbox/maps'; import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; +import useOnyx from '@hooks/useOnyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {clearUserLocation, setUserLocation} from '@libs/actions/UserLocation'; diff --git a/src/components/MapView/MapViewImpl.website.tsx b/src/components/MapView/MapViewImpl.website.tsx index 4fdf71252895..0a3bbf57c9f7 100644 --- a/src/components/MapView/MapViewImpl.website.tsx +++ b/src/components/MapView/MapViewImpl.website.tsx @@ -9,10 +9,10 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, import type {MapRef, ViewState} from 'react-map-gl'; import Map, {Marker} from 'react-map-gl'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithoutFeedback} from '@components/Pressable'; +import useOnyx from '@hooks/useOnyx'; import usePrevious from '@hooks/usePrevious'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; diff --git a/src/components/ReportActionItem/IssueCardMessage.tsx b/src/components/ReportActionItem/IssueCardMessage.tsx index f0e22c9a731b..cccbaed6faaa 100644 --- a/src/components/ReportActionItem/IssueCardMessage.tsx +++ b/src/components/ReportActionItem/IssueCardMessage.tsx @@ -1,9 +1,9 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import RenderHTML from '@components/RenderHTML'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 6354b69bc58e..d0fee90d6c51 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -2,7 +2,6 @@ import {Str} from 'expensify-common'; import React, {useMemo} from 'react'; import type {StyleProp, TextStyle} from 'react-native'; import {ActivityIndicator, View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -13,6 +12,7 @@ import Text from '@components/Text'; import UnreadActionIndicator from '@components/UnreadActionIndicator'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useOnyx from '@hooks/useOnyx'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 2936fddd0376..a2faa40a468b 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -1,9 +1,9 @@ import React from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import RenderHTML from '@components/RenderHTML'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {isIOUReportPendingCurrencyConversion} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 8c1c1287d77b..f1ca0a698ab5 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -4,7 +4,6 @@ import truncate from 'lodash/truncate'; import React, {useMemo} from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import Button from '@components/Button'; import Icon from '@components/Icon'; @@ -18,6 +17,7 @@ import ReportActionItemImages from '@components/ReportActionItem/ReportActionIte import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -374,6 +374,7 @@ function MoneyRequestPreviewContent({ style={[ isScanning || isWhisper ? [styles.reportPreviewBoxHoverBorder, styles.reportContainerBorderRadius] : undefined, !onPreviewPressed ? [styles.moneyRequestPreviewBox, containerStyles] : {}, + styles.borderedContentCardLarge, ]} > {!isDeleted && ( diff --git a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx index 0b9d4e5f5629..8c0502c8a3dd 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx @@ -1,6 +1,6 @@ import lodashIsEmpty from 'lodash/isEmpty'; import React from 'react'; -import {useOnyx} from 'react-native-onyx'; +import useOnyx from '@hooks/useOnyx'; import ONYXKEYS from '@src/ONYXKEYS'; import MoneyRequestPreviewContent from './MoneyRequestPreviewContent'; import type {MoneyRequestPreviewProps} from './types'; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 44ce02724cd5..adb105d57946 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; @@ -15,6 +14,7 @@ import ViolationMessages from '@components/ViolationMessages'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import useViolations from '@hooks/useViolations'; import type {ViolationField} from '@hooks/useViolations'; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index dec1e96d82b6..dc31233ff7e2 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -2,7 +2,6 @@ import truncate from 'lodash/truncate'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withDelay, withSpring, withTiming} from 'react-native-reanimated'; import Button from '@components/Button'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; @@ -18,6 +17,7 @@ import Text from '@components/Text'; import useDelegateUserDetails from '@hooks/useDelegateUserDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -163,7 +163,7 @@ function ReportPreview({ const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ - hasMissingSmartscanFields: hasMissingSmartscanFieldsReportUtils(iouReportID), + hasMissingSmartscanFields: hasMissingSmartscanFieldsReportUtils(iouReportID, transactions), areAllRequestsBeingSmartScanned: areAllRequestsBeingSmartScannedReportUtils(iouReportID, action), hasOnlyTransactionsWithPendingRoutes: hasOnlyTransactionsWithPendingRoutesReportUtils(iouReportID), hasNonReimbursableTransactions: hasNonReimbursableTransactionsReportUtils(iouReportID), @@ -542,7 +542,7 @@ function ReportPreview({ onPressOut={() => ControlSelection.unblock()} onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)} shouldUseHapticsOnLongPress - style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} + style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox, styles.borderedContentCardLarge]} role="button" accessibilityLabel={translate('iou.viewDetails')} > diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 495b3dbd51fd..27aa7310af85 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -2,7 +2,6 @@ import {Str} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import Avatar from '@components/Avatar'; import Checkbox from '@components/Checkbox'; @@ -17,6 +16,7 @@ import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/ReportActionItem/TripRoomPreview.tsx b/src/components/ReportActionItem/TripRoomPreview.tsx index de8a559c602a..f895fcda4099 100644 --- a/src/components/ReportActionItem/TripRoomPreview.tsx +++ b/src/components/ReportActionItem/TripRoomPreview.tsx @@ -2,7 +2,6 @@ import {Str} from 'expensify-common'; import React, {useMemo} from 'react'; import type {ListRenderItemInfo, StyleProp, ViewStyle} from 'react-native'; import {FlatList, View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import Icon from '@components/Icon'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -11,6 +10,7 @@ import {PressableWithoutFeedback} from '@components/Pressable'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 3c96dfd28752..c58f64079050 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -1,8 +1,8 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index f6f0a62f2c9f..bdae42a291c0 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -1,21 +1,11 @@ -import React, {useMemo} from 'react'; -import {View} from 'react-native'; -import {AttachmentContext} from '@components/AttachmentContext'; -import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; -import MultipleAvatars from '@components/MultipleAvatars'; -import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; -import Text from '@components/Text'; -import TextLink from '@components/TextLink'; -import TextWithTooltip from '@components/TextWithTooltip'; +import React from 'react'; import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; -import useLocalize from '@hooks/useLocalize'; -import useStyleUtils from '@hooks/useStyleUtils'; +import useOnyx from '@hooks/useOnyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import ReportActionItemDate from '@pages/home/report/ReportActionItemDate'; -import ReportActionItemFragment from '@pages/home/report/ReportActionItemFragment'; +import ReportActionItem from '@pages/home/report/ReportActionItem'; import variables from '@styles/variables'; -import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import BaseListItem from './BaseListItem'; import type {ChatListItemProps, ListItem, ReportActionListItemType} from './types'; @@ -32,35 +22,8 @@ function ChatListItem({ shouldSyncFocus, }: ChatListItemProps) { const reportActionItem = item as unknown as ReportActionListItemType; - const from = reportActionItem.from; - const icons = [ - { - type: CONST.ICON_TYPE_AVATAR, - source: from.avatar, - name: reportActionItem.formattedFrom, - id: from.accountID, - }, - ]; const styles = useThemeStyles(); const theme = useTheme(); - const StyleUtils = useStyleUtils(); - const {translate} = useLocalize(); - - const attachmentContextValue = {type: CONST.ATTACHMENT_TYPE.SEARCH}; - - const contextValue = { - anchor: null, - report: undefined, - reportNameValuePairs: undefined, - action: undefined, - transactionThreadReport: undefined, - checkIfContextMenuActive: () => {}, - isDisabled: true, - }; - - const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; - const hoveredBackgroundColor = styles.sidebarLinkHover?.backgroundColor ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; - const mentionReportContextValue = useMemo(() => ({currentReportID: item?.reportID}), [item.reportID]); const animatedHighlightStyle = useAnimatedHighlightStyle({ borderRadius: variables.componentBorderRadius, shouldHighlight: item?.shouldAnimateInHighlight ?? false, @@ -69,6 +32,7 @@ function ChatListItem({ }); const pressableStyle = [ styles.selectionListPressableItemWrapper, + styles.p0, styles.textAlignLeft, styles.overflowHidden, // Removing background style because they are added to the parent OpacityView via animatedHighlightStyle @@ -77,11 +41,13 @@ function ChatListItem({ styles.mh0, item.cursorStyle, ]; + + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportActionItem?.reportID}`); return ( ({ pressableWrapperStyle={[styles.mh5, animatedHighlightStyle]} hoverStyle={item.isSelected && styles.activeComponentBG} > - {(hovered) => ( - - - - - - {translate('common.in')}  - onSelectRow(item)} - numberOfLines={1} - > - {reportActionItem.reportName} - - - - - - - - - - - - - {reportActionItem.message.map((fragment, index) => ( - - ))} - - - - - - - - )} + onSelectRow(item)} + reportActions={[]} + parentReportAction={undefined} + displayAsGroup={false} + isMostRecentIOUReportAction={false} + shouldDisplayNewMarker={false} + index={item.index ?? 0} + isFirstVisibleReportAction={false} + shouldDisplayContextMenu={false} + /> ); } diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx index c55edd9e6b15..9620b2b799c6 100644 --- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx +++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx @@ -1,13 +1,13 @@ import {Str} from 'expensify-common'; import React, {useCallback} from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Avatar from '@components/Avatar'; import {usePersonalDetails} from '@components/OnyxProvider'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import type UserDetailsTooltipProps from '@components/UserDetailsTooltip/types'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {isAnonymousUser} from '@libs/actions/Session'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; diff --git a/src/components/VideoPlayerContexts/PlaybackContext.tsx b/src/components/VideoPlayerContexts/PlaybackContext.tsx index 6a971deacbc1..8c7e82f74eaf 100644 --- a/src/components/VideoPlayerContexts/PlaybackContext.tsx +++ b/src/components/VideoPlayerContexts/PlaybackContext.tsx @@ -131,6 +131,7 @@ function PlaybackContextProvider({children}: ChildrenProps) { pauseVideo, checkVideoPlaying, videoResumeTryNumberRef, + resetVideoPlayerData, }), [ updateCurrentlyPlayingURL, @@ -143,6 +144,7 @@ function PlaybackContextProvider({children}: ChildrenProps) { pauseVideo, checkVideoPlaying, setCurrentlyPlayingURL, + resetVideoPlayerData, ], ); return {children}; diff --git a/src/components/VideoPlayerContexts/types.ts b/src/components/VideoPlayerContexts/types.ts index 532d3a0131d3..c0bf0297b440 100644 --- a/src/components/VideoPlayerContexts/types.ts +++ b/src/components/VideoPlayerContexts/types.ts @@ -20,6 +20,7 @@ type PlaybackContext = { pauseVideo: () => void; checkVideoPlaying: (statusCallback: StatusCallback) => void; setCurrentlyPlayingURL: React.Dispatch>; + resetVideoPlayerData: () => void; }; type VolumeContext = { diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx index fb188e593949..03ce41ad2713 100644 --- a/src/components/VideoPlayerPreview/index.tsx +++ b/src/components/VideoPlayerPreview/index.tsx @@ -8,6 +8,7 @@ import IconButton from '@components/VideoPlayer/IconButton'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useSearchState from '@hooks/useSearchState'; import useThemeStyles from '@hooks/useThemeStyles'; import useThumbnailDimensions from '@hooks/useThumbnailDimensions'; import VideoPlayerThumbnail from './VideoPlayerThumbnail'; @@ -51,7 +52,7 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi const [isThumbnail, setIsThumbnail] = useState(true); const [measuredDimensions, setMeasuredDimensions] = useState(videoDimensions); const {thumbnailDimensionsStyles} = useThumbnailDimensions(measuredDimensions.width, measuredDimensions.height); - + const {isOnSearch} = useSearchState(); // `onVideoLoaded` is passed to VideoPlayerPreview's `Video` element which is displayed only on web. // VideoReadyForDisplayEvent type is lacking srcElement, that's why it's added here const onVideoLoaded = (event: VideoReadyForDisplayEvent & {srcElement: HTMLVideoElement}) => { @@ -66,11 +67,11 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi }; useEffect(() => { - if (videoUrl !== currentlyPlayingURL || reportID !== currentlyPlayingURLReportID) { + if (videoUrl !== currentlyPlayingURL || (reportID !== currentlyPlayingURLReportID && !isOnSearch)) { return; } setIsThumbnail(false); - }, [currentlyPlayingURL, currentlyPlayingURLReportID, updateCurrentlyPlayingURL, videoUrl, reportID]); + }, [currentlyPlayingURL, currentlyPlayingURLReportID, updateCurrentlyPlayingURL, videoUrl, reportID, isOnSearch]); return ( diff --git a/src/hooks/useOnyx.ts b/src/hooks/useOnyx.ts new file mode 100644 index 000000000000..ae78bf1b979c --- /dev/null +++ b/src/hooks/useOnyx.ts @@ -0,0 +1,82 @@ +import {useMemo} from 'react'; +import {useOnyx as originalUseOnyx} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, UseOnyxOptions} from 'react-native-onyx'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {SearchResults} from '@src/types/onyx'; +import useSearchState from './useSearchState'; + +type OriginalUseOnyx = typeof originalUseOnyx; +type OriginalUseOnyxReturnType = ReturnType; + +const getDataByPath = (data: SearchResults['data'], path: string) => { + // Handle prefixed collections + for (const collection of Object.values(ONYXKEYS.COLLECTION)) { + if (path.startsWith(collection)) { + const key = `${collection}${path.slice(collection.length)}`; + return data?.[key as keyof typeof data]; + } + } + + // Handle direct keys + return data?.[path as keyof typeof data]; +}; + +// Helper function to get key data from snapshot +const getKeyData = (snapshotData: SearchResults, key: TKey, initialValue?: TReturnValue): TReturnValue => { + if (key.endsWith('_')) { + // Create object to store matching entries + const result: OnyxCollection = {}; + const prefix = key; + + // Get all keys that start with the prefix + Object.entries(snapshotData?.data ?? {}).forEach(([dataKey, value]) => { + if (!dataKey.startsWith(prefix)) { + return; + } + result[dataKey] = value as OnyxEntry; + }); + return (Object.keys(result).length > 0 ? result : initialValue) as TReturnValue; + } + return (getDataByPath(snapshotData?.data, key) ?? initialValue) as TReturnValue; +}; + +/** + * Custom hook for accessing and subscribing to Onyx data with search snapshot support + */ +const useOnyx: OriginalUseOnyx = (key, options, dependencies) => { + const {isOnSearch, hashKey} = useSearchState(); + const useOnyxOptions = options as UseOnyxOptions> | undefined; + const {selector: selectorProp, ...optionsWithoutSelector} = useOnyxOptions ?? {}; + + // Determine if we should use snapshot data based on search state and key + const shouldUseSnapshot = isOnSearch && !key.startsWith(ONYXKEYS.COLLECTION.SNAPSHOT) && CONST.SEARCH.SNAPSHOT_ONYX_KEYS.some((snapshotKey) => key.startsWith(snapshotKey)); + + // Create selector function that handles both regular and snapshot data + const selector = useMemo(() => { + if (!selectorProp) { + return undefined; + } + return (data: OnyxValue | undefined) => selectorProp(shouldUseSnapshot ? getKeyData(data as SearchResults, key) : data); + }, [selectorProp, shouldUseSnapshot, key]); + + const onyxOptions: UseOnyxOptions> = {...optionsWithoutSelector, selector, allowDynamicKey: true}; + const snapshotKey = shouldUseSnapshot ? (`${ONYXKEYS.COLLECTION.SNAPSHOT}${hashKey}` as OnyxKey) : key; + + const originalResult = originalUseOnyx(snapshotKey, onyxOptions, dependencies); + + // Extract and memoize the specific key data from snapshot if in search mode + const result = useMemo((): OriginalUseOnyxReturnType => { + // if it has selector, we wouldn't need to use snapshot here + if (!shouldUseSnapshot || selector) { + return originalResult as OriginalUseOnyxReturnType; + } + + const keyData = getKeyData(originalResult[0] as SearchResults, key, useOnyxOptions?.initialValue); + return [keyData, originalResult[1]] as OriginalUseOnyxReturnType; + }, [shouldUseSnapshot, originalResult, key, useOnyxOptions?.initialValue, selector]); + + return result; +}; + +export default useOnyx; diff --git a/src/hooks/usePolicy.ts b/src/hooks/usePolicy.ts index f08b1b323022..e3d5ec1a1659 100644 --- a/src/hooks/usePolicy.ts +++ b/src/hooks/usePolicy.ts @@ -1,6 +1,6 @@ -import {useOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import useOnyx from './useOnyx'; function getPolicyIDOrDefault(policyID?: string) { if (!policyID || policyID === CONST.POLICY.OWNER_EMAIL_FAKE) { diff --git a/src/hooks/useSearchState.ts b/src/hooks/useSearchState.ts new file mode 100644 index 000000000000..055c7b8274df --- /dev/null +++ b/src/hooks/useSearchState.ts @@ -0,0 +1,44 @@ +import {NavigationRouteContext} from '@react-navigation/native'; +import {useContext, useMemo} from 'react'; +import type {SearchQueryJSON} from '@components/Search/types'; +import {buildSearchQueryJSON} from '@libs/SearchQueryUtils'; +import CONST from '@src/CONST'; +import SCREENS from '@src/SCREENS'; + +type SearchRouteParams = { + q?: string; + type?: string; + hashKey?: number; +}; + +type SearchStateResult = { + isOnSearch: boolean; + hashKey?: number; +}; + +/** + * Hook to manage search state based on route parameters + * Returns search status and hash for query tracking + */ +const useSearchState = (): SearchStateResult => { + // We are using these contexts directly instead of useRoute, because those will throw an error if used outside a navigator. + const route = useContext(NavigationRouteContext); + const {q, type, hashKey: hashKeyFromRoute} = (route?.params as SearchRouteParams) ?? {}; + + return useMemo(() => { + const isSearchAttachmentModal = route?.name === SCREENS.ATTACHMENTS && type === CONST.ATTACHMENT_TYPE.SEARCH; + + if (!route) { + return {isOnSearch: false, hashKey: undefined}; + } + + const queryJSON = q ? buildSearchQueryJSON(q) : ({} as Partial); + // for attachment modal the hashKey is passed through route params, fallback to it if not found in queryJSON + const hashKey = queryJSON?.hash ? queryJSON.hash : hashKeyFromRoute ?? undefined; + const isOnSearch = (route?.name === SCREENS.SEARCH.ROOT && !!hashKey) || isSearchAttachmentModal; + + return {hashKey, isOnSearch}; + }, [q, type, route, hashKeyFromRoute]); +}; + +export default useSearchState; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index fa274d1ecf7e..1dc6cf919cae 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1758,6 +1758,7 @@ type AuthScreensParamList = SharedScreensParamList & { isAuthTokenRequired?: string; fileName?: string; attachmentLink?: string; + hashKey?: number; }; [SCREENS.PROFILE_AVATAR]: { accountID: string; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a7a67f6de26f..7009a4d0a5c2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3772,8 +3772,8 @@ function getLinkedTransaction(reportAction: OnyxEntry Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: buildCannedSearchQuery()})); + const {resetVideoPlayerData} = usePlaybackContext(); const {clearSelectedTransactions} = useSearchContext(); const isSearchNameModified = name === q; const searchName = isSearchNameModified ? undefined : name; + // Handles video player cleanup: + // 1. On mount: Resets player if navigating from report screen + // 2. On unmount: Stops video when leaving this screen + // in narrow layout, the reset will be handled by the attachment modal, so we don't need to do it here to preserve autoplay + useEffect(() => { + if (shouldUseNarrowLayout) { + return; + } + resetVideoPlayerData(); + return () => { + if (shouldUseNarrowLayout) { + return; + } + resetVideoPlayerData(); + }; + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + if (shouldUseNarrowLayout) { return ( diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index c28fdeda89f8..9c8d5ef1cac0 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -5,7 +5,6 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import type {FlatList, ViewStyle} from 'react-native'; import {DeviceEventEmitter, InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import Banner from '@components/Banner'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; @@ -22,6 +21,7 @@ import useCurrentReportID from '@hooks/useCurrentReportID'; import useDeepCompareRef from '@hooks/useDeepCompareRef'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useOnyx from '@hooks/useOnyx'; import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 49a855fc690b..2ca603fff194 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -34,6 +34,7 @@ import UnreadActionIndicator from '@components/UnreadActionIndicator'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useSearchState from '@hooks/useSearchState'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -355,7 +356,7 @@ function PureReportActionItem({ }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const reportID = report?.reportID; + const reportID = report?.reportID ?? action?.reportID; const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -398,6 +399,7 @@ function PureReportActionItem({ [action.reportActionID, action.message, updateHiddenAttachments], ); + const {isOnSearch, hashKey} = useSearchState(); const onClose = () => { let transactionID; if (isMoneyRequestAction(action)) { @@ -567,9 +569,14 @@ function PureReportActionItem({ [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs], ); - const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]); + const attachmentContextValue = useMemo(() => { + if (isOnSearch) { + return {type: CONST.ATTACHMENT_TYPE.SEARCH, hashKey}; + } + return {reportID, type: CONST.ATTACHMENT_TYPE.REPORT}; + }, [reportID, isOnSearch, hashKey]); - const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID}), [report?.reportID]); + const mentionReportContextValue = useMemo(() => ({currentReportID: reportID}), [reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { if (isActionableAddPaymentCard(action) && userBillingFundID === undefined && shouldRenderAddPaymentCard()) { return [ @@ -989,7 +996,7 @@ function PureReportActionItem({ { if (isAnonymousUser()) { diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 5186c1c95578..6970b142c108 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,7 +1,7 @@ import React, {useMemo} from 'react'; -import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; -import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; +import {useBlockedFromConcierge} from '@components/OnyxProvider'; +import useOnyx from '@hooks/useOnyx'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -38,7 +38,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || undefined}`); - const personalDetails = usePersonalDetails(); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const blockedFromConcierge = useBlockedFromConcierge(); const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; diff --git a/src/pages/home/report/ReportActionItemContentCreated.tsx b/src/pages/home/report/ReportActionItemContentCreated.tsx index 7c0016b56338..d1a29bc14f45 100644 --- a/src/pages/home/report/ReportActionItemContentCreated.tsx +++ b/src/pages/home/report/ReportActionItemContentCreated.tsx @@ -1,7 +1,6 @@ import lodashIsEqual from 'lodash/isEqual'; import React, {memo, useMemo} from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import RenderHTML from '@components/RenderHTML'; @@ -13,6 +12,7 @@ import type {ShowContextMenuContextProps} from '@components/ShowContextMenuConte import SpacerView from '@components/SpacerView'; import UnreadActionIndicator from '@components/UnreadActionIndicator'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import {isMessageDeleted, isReversedTransaction as isReversedTransactionReportActionsUtils, isTransactionThread} from '@libs/ReportActionsUtils'; diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx index f36048ee2125..573caf4486e1 100644 --- a/src/pages/home/report/ReportActionItemMessage.tsx +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -2,10 +2,10 @@ import type {ReactElement} from 'react'; import React from 'react'; import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import { diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index e417823262de..7641728ff065 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -2,19 +2,20 @@ import React, {useCallback, useMemo} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import Avatar from '@components/Avatar'; import {FallbackAvatar} from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {usePersonalDetails} from '@components/OnyxProvider'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; +import TextLink from '@components/TextLink'; import Tooltip from '@components/Tooltip'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; +import useSearchState from '@hooks/useSearchState'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -37,11 +38,13 @@ import { isPolicyExpenseChat, isTripRoom as isTripRoomReportUtils, } from '@libs/ReportUtils'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Report, ReportAction} from '@src/types/onyx'; import type {Icon} from '@src/types/onyx/OnyxCommon'; +import type {SearchReportAction} from '@src/types/onyx/SearchResults'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import ReportActionItemDate from './ReportActionItemDate'; import ReportActionItemFragment from './ReportActionItemFragment'; @@ -73,6 +76,9 @@ type ReportActionItemSingleProps = Partial & { /** If the action is being actived */ isActive?: boolean; + + /** Callback to be called on onPress */ + onPress?: () => void; }; const showUserDetails = (accountID: number | undefined) => { @@ -97,12 +103,13 @@ function ReportActionItemSingle({ iouReport, isHovered = false, isActive = false, + onPress = undefined, }: ReportActionItemSingleProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const personalDetails = usePersonalDetails(); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const policy = usePolicy(report?.policyID); const delegatePersonalDetails = action?.delegateAccountID ? personalDetails?.[action?.delegateAccountID] : undefined; const ownerAccountID = iouReport?.ownerAccountID ?? action?.childOwnerAccountID; @@ -112,7 +119,7 @@ function ReportActionItemSingle({ `${ONYXKEYS.COLLECTION.POLICY}${report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`, ); - let displayName = getDisplayNameForParticipant({accountID: actorAccountID}); + let displayName = getDisplayNameForParticipant({accountID: actorAccountID, personalDetailsData: personalDetails}); const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails?.[actorAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? {}; const accountOwnerDetails = getPersonalDetailByEmail(login ?? ''); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -121,6 +128,7 @@ function ReportActionItemSingle({ const displayAllActors = isReportPreviewAction && !isTripRoom && !isPolicyExpenseChat(report); const isInvoiceReport = isInvoiceReportUtils(iouReport ?? null); const isWorkspaceActor = isInvoiceReport || (isPolicyExpenseChat(report) && (!actorAccountID || displayAllActors)); + const {isOnSearch} = useSearchState(); let avatarSource = avatar; let avatarId: number | string | undefined = actorAccountID; @@ -168,7 +176,7 @@ function ReportActionItemSingle({ } else if (!isWorkspaceActor) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const avatarIconIndex = report?.isOwnPolicyExpenseChat || isPolicyExpenseChat(report) ? 0 : 1; - const reportIcons = getIcons(report, {}); + const reportIcons = getIcons(report, personalDetails, undefined, undefined, undefined, policy); secondaryAvatar = reportIcons.at(avatarIconIndex) ?? {name: '', source: '', type: CONST.ICON_TYPE_AVATAR}; } else if (isInvoiceReportUtils(iouReport)) { @@ -281,8 +289,8 @@ function ReportActionItemSingle({ const statusText = status?.text ?? ''; const statusTooltipText = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; - return ( - + const reportActionContent = ( + <> {children} + + ); + + if (!isOnSearch) { + return {reportActionContent}; + } + + return ( + + + + {translate('common.in')}  + { + onPress?.(); + }} + numberOfLines={1} + > + {(action as SearchReportAction).reportName} + + + {reportActionContent} + ); } diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index dfac22b23f06..bddc05892d6a 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -16,6 +16,7 @@ type ReportAttachmentsProps = PlatformStackScreenProps borderRadius: variables.componentBorderRadiusNormal, }, + borderedContentCardLarge: { + borderWidth: 1, + borderColor: theme.border, + borderRadius: variables.componentBorderRadiusLarge, + }, + sectionMenuItem: { borderRadius: 8, paddingHorizontal: 16, diff --git a/tests/actions/AppTest.ts b/tests/actions/AppTest.ts index 8e0952c94781..c611df934800 100644 --- a/tests/actions/AppTest.ts +++ b/tests/actions/AppTest.ts @@ -8,6 +8,8 @@ import getOnyxValue from '../utils/getOnyxValue'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +jest.mock('@src/components/ConfirmedRoute.tsx'); + OnyxUpdateManager(); describe('actions/App', () => { diff --git a/tests/perf-test/SearchRouter.perf-test.tsx b/tests/perf-test/SearchRouter.perf-test.tsx index 95807a12cd1b..365788a6156f 100644 --- a/tests/perf-test/SearchRouter.perf-test.tsx +++ b/tests/perf-test/SearchRouter.perf-test.tsx @@ -69,6 +69,7 @@ jest.mock('@react-navigation/native', () => { }; }); +jest.mock('@src/components/ConfirmedRoute.tsx'); jest.mock('@libs/runOnLiveMarkdownRuntime', () => { const runOnLiveMarkdownRuntime = (worklet: (...args: Args) => ReturnValue) => worklet; return runOnLiveMarkdownRuntime; diff --git a/tests/perf-test/SelectionList.perf-test.tsx b/tests/perf-test/SelectionList.perf-test.tsx index fcd714129536..759ab1a8da22 100644 --- a/tests/perf-test/SelectionList.perf-test.tsx +++ b/tests/perf-test/SelectionList.perf-test.tsx @@ -84,6 +84,8 @@ jest.mock('../../src/hooks/useScreenWrapperTransitionStatus', () => ({ })), })); +jest.mock('@src/components/ConfirmedRoute.tsx'); + function SelectionListWrapper({canSelectMultiple}: SelectionListWrapperProps) { const [selectedIds, setSelectedIds] = useState([]); diff --git a/tests/ui/NewChatPageTest.tsx b/tests/ui/NewChatPageTest.tsx index 837f59bf3af4..bd500635f969 100644 --- a/tests/ui/NewChatPageTest.tsx +++ b/tests/ui/NewChatPageTest.tsx @@ -15,6 +15,7 @@ import {fakePersonalDetails} from '../utils/LHNTestUtils'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; jest.mock('@react-navigation/native'); +jest.mock('@components/ConfirmedRoute.tsx'); describe('NewChatPage', () => { beforeAll(() => { diff --git a/tests/ui/WorkspaceCategoriesTest.tsx b/tests/ui/WorkspaceCategoriesTest.tsx index cdcba7295c0e..1f470371f643 100644 --- a/tests/ui/WorkspaceCategoriesTest.tsx +++ b/tests/ui/WorkspaceCategoriesTest.tsx @@ -20,6 +20,8 @@ import * as LHNTestUtils from '../utils/LHNTestUtils'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; +jest.mock('@src/components/ConfirmedRoute.tsx'); + TestHelper.setupGlobalFetchMock(); const Stack = createPlatformStackNavigator(); diff --git a/tests/ui/WorkspaceSwitcherTest.tsx b/tests/ui/WorkspaceSwitcherTest.tsx index 9c85369843fd..fdfd5caf3db2 100644 --- a/tests/ui/WorkspaceSwitcherTest.tsx +++ b/tests/ui/WorkspaceSwitcherTest.tsx @@ -26,6 +26,8 @@ jest.mock('@react-navigation/native', () => { triggerTransitionEnd: jest.fn(), }; }); + +jest.mock('@src/components/ConfirmedRoute.tsx'); jest.mock('@src/components/Navigation/TopLevelBottomTabBar/useIsBottomTabVisibleDirectly'); TestHelper.setupApp(); diff --git a/tests/ui/components/ReportPreviewTest.tsx b/tests/ui/components/ReportPreviewTest.tsx index 5e5a5e4b5787..b74fd0178d89 100644 --- a/tests/ui/components/ReportPreviewTest.tsx +++ b/tests/ui/components/ReportPreviewTest.tsx @@ -5,6 +5,7 @@ import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxProvider from '@components/OnyxProvider'; import ReportPreview from '@components/ReportActionItem/ReportPreview'; import {translateLocal} from '@libs/Localize'; +import type Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import createRandomReportAction from '../../utils/collections/reportActions'; @@ -23,6 +24,14 @@ jest.mock('@react-native-community/geolocation', () => ({ setRNConfiguration: jest.fn(), })); +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useRoute: () => jest.fn(), + }; +}); + describe('ReportPreview', () => { afterEach(() => { jest.clearAllMocks(); diff --git a/tests/unit/BaseSelectionListTest.tsx b/tests/unit/BaseSelectionListTest.tsx index 07ac5f4667ca..ea7980ee6804 100644 --- a/tests/unit/BaseSelectionListTest.tsx +++ b/tests/unit/BaseSelectionListTest.tsx @@ -17,6 +17,7 @@ const mockSections = Array.from({length: 10}, (_, index) => ({ isSelected: index === 1, })); +jest.mock('@src/components/ConfirmedRoute.tsx'); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { diff --git a/tests/unit/CalendarPickerTest.tsx b/tests/unit/CalendarPickerTest.tsx index f7bee25e366f..322d47fc289a 100644 --- a/tests/unit/CalendarPickerTest.tsx +++ b/tests/unit/CalendarPickerTest.tsx @@ -36,6 +36,8 @@ jest.mock('../../src/hooks/useLocalize', () => })), ); +jest.mock('@src/components/ConfirmedRoute.tsx'); + describe('CalendarPicker', () => { test('renders calendar component', () => { render(); diff --git a/tests/unit/GoogleTagManagerTest.tsx b/tests/unit/GoogleTagManagerTest.tsx index 327867361081..c310d2dd5155 100644 --- a/tests/unit/GoogleTagManagerTest.tsx +++ b/tests/unit/GoogleTagManagerTest.tsx @@ -14,6 +14,7 @@ jest.mock('@libs/GoogleTagManager'); // Mock the Overlay since it doesn't work in tests jest.mock('@libs/Navigation/AppNavigator/Navigators/Overlay'); +jest.mock('@src/components/ConfirmedRoute.tsx'); describe('GoogleTagManagerTest', () => { const accountID = 123456; diff --git a/tests/unit/ReportActionItemSingleTest.ts b/tests/unit/ReportActionItemSingleTest.ts index e0bf06be1609..f8a29e87af82 100644 --- a/tests/unit/ReportActionItemSingleTest.ts +++ b/tests/unit/ReportActionItemSingleTest.ts @@ -54,16 +54,18 @@ describe('ReportActionItemSingle', () => { }; function setup() { - LHNTestUtils.getDefaultRenderedReportActionItemSingle(shouldShowSubscriptAvatar, fakeReport, fakeReportAction); const policyCollectionDataSet = toCollectionDataSet(ONYXKEYS.COLLECTION.POLICY, [fakePolicy], (item) => item.id); - - return waitForBatchedUpdates().then(() => - Onyx.multiSet({ - [ONYXKEYS.PERSONAL_DETAILS_LIST]: fakePersonalDetails, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, - ...policyCollectionDataSet, - }), - ); + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: fakePersonalDetails, + [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + ...policyCollectionDataSet, + }), + ) + .then(() => { + LHNTestUtils.getDefaultRenderedReportActionItemSingle(shouldShowSubscriptAvatar, fakeReport, fakeReportAction); + }); } it('renders secondary Avatar properly', async () => { @@ -75,10 +77,11 @@ describe('ReportActionItemSingle', () => { }); }); - it('renders Person information', () => { + it('renders Person information', async () => { const [expectedPerson] = fakeReportAction.person ?? []; - return setup().then(() => { + await setup(); + await waitFor(() => { expect(screen.getByText(expectedPerson.text ?? '')).toBeOnTheScreen(); }); }); diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 5afcbcce30ae..cc005243fe34 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -3,6 +3,8 @@ import CONST from '@src/CONST'; import * as SearchUIUtils from '@src/libs/SearchUIUtils'; import type * as OnyxTypes from '@src/types/onyx'; +jest.mock('@src/components/ConfirmedRoute.tsx'); + const accountID = 18439984; const policyID = 'A1B2C3'; const reportID = '123456789'; diff --git a/tests/unit/Search/buildCardFilterDataTest.ts b/tests/unit/Search/buildCardFilterDataTest.ts index 25736889db57..11f5b8fe2bff 100644 --- a/tests/unit/Search/buildCardFilterDataTest.ts +++ b/tests/unit/Search/buildCardFilterDataTest.ts @@ -7,6 +7,7 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import {buildCardFeedsData, buildIndividualCardsData} from '@pages/Search/SearchAdvancedFiltersPage/SearchFiltersCardPage'; import type {CardList, Policy, WorkspaceCardsList} from '@src/types/onyx'; +jest.mock('@src/components/ConfirmedRoute.tsx'); // Use jest.spyOn to mock the implementation jest.spyOn(PolicyUtils, 'getPolicy').mockImplementation((policyID?: string): Policy => { switch (policyID) { diff --git a/tests/unit/Search/handleActionButtonPressTest.ts b/tests/unit/Search/handleActionButtonPressTest.ts index 69af0e83849a..5082913a3889 100644 --- a/tests/unit/Search/handleActionButtonPressTest.ts +++ b/tests/unit/Search/handleActionButtonPressTest.ts @@ -2,6 +2,8 @@ import type {ReportListItemType} from '@components/SelectionList/types'; import {handleActionButtonPress} from '@libs/actions/Search'; +jest.mock('@src/components/ConfirmedRoute.tsx'); + const mockReportItemWithHold = { shouldAnimateInHighlight: false, accountID: 1206, diff --git a/tests/unit/useSearchHighlightAndScrollTest.ts b/tests/unit/useSearchHighlightAndScrollTest.ts index bc8ccd7ecb30..8fc6aed25780 100644 --- a/tests/unit/useSearchHighlightAndScrollTest.ts +++ b/tests/unit/useSearchHighlightAndScrollTest.ts @@ -5,6 +5,7 @@ import type {UseSearchHighlightAndScroll} from '@hooks/useSearchHighlightAndScro import * as Search from '@libs/actions/Search'; jest.mock('@libs/actions/Search'); +jest.mock('@src/components/ConfirmedRoute.tsx'); describe('useSearchHighlightAndScroll', () => { it('should trigger Search when transactionIDs list change', () => {