From ba9cd58eda4aedabf31d6e9056f78da3592f1221 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 24 Apr 2024 07:28:10 +0700 Subject: [PATCH 01/13] fix error when clicking on attachment note --- src/CONST.ts | 6 ++ src/ROUTES.ts | 6 +- src/SCREENS.ts | 2 +- src/components/AttachmentContext.ts | 17 ++++ .../HTMLRenderers/ImageRenderer.tsx | 36 +++++--- .../HTMLRenderers/VideoRenderer.tsx | 2 +- src/libs/Middleware/SaveResponseInOnyx.ts | 1 + .../Navigation/AppNavigator/AuthScreens.tsx | 2 +- src/libs/Navigation/NavigationRoot.tsx | 1 + src/libs/Navigation/dismissModal.ts | 2 +- src/libs/Navigation/dismissModalWithReport.ts | 2 +- src/libs/Navigation/linkTo.ts | 10 ++- src/libs/Navigation/linkingConfig/config.ts | 3 +- .../linkingConfig/getAdaptedStateFromPath.ts | 30 ++++--- src/libs/Navigation/types.ts | 5 +- .../PrivateNotes/PrivateNotesListPage.tsx | 28 +++--- src/pages/home/report/ReportActionItem.tsx | 89 ++++++++++--------- src/pages/home/report/ReportAttachments.tsx | 7 +- 18 files changed, 155 insertions(+), 94 deletions(-) create mode 100644 src/components/AttachmentContext.ts diff --git a/src/CONST.ts b/src/CONST.ts index 2cd614b74816..3d8545d74088 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1132,6 +1132,12 @@ const CONST = { WEBP: 'image/webp', JPEG: 'image/jpeg', }, + ATTACHMENT_TYPE: { + REPORT: 'r', + REPORT_ACTION: 'a', + NOTE: 'n', + SINGLE: 's', + }, FILE_TYPE_REGEX: { // Image MimeTypes allowed by iOS photos app. diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ceb4c217cb6e..47fe2516c7d7 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -225,9 +225,9 @@ const ROUTES = { route: 'r/:reportID/details/shareCode', getRoute: (reportID: string) => `r/${reportID}/details/shareCode` as const, }, - REPORT_ATTACHMENTS: { - route: 'r/:reportID/attachment', - getRoute: (reportID: string, source: string) => `r/${reportID}/attachment?source=${encodeURIComponent(source)}` as const, + ATTACHMENTS: { + route: 'attachment', + getRoute: (id: string, type: ValueOf, url: string) => `attachment?source=${encodeURIComponent(url)}&type=${type}${id ? `&id=${id}` : ''}`, }, REPORT_PARTICIPANTS: { route: 'r/:reportID/participants', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index c839d9d05070..5fc6b8cbd8d8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -7,7 +7,7 @@ import type DeepValueOf from './types/utils/DeepValueOf'; const PROTECTED_SCREENS = { HOME: 'Home', CONCIERGE: 'Concierge', - REPORT_ATTACHMENTS: 'ReportAttachments', + ATTACHMENTS: 'Attachments', } as const; const SCREENS = { diff --git a/src/components/AttachmentContext.ts b/src/components/AttachmentContext.ts new file mode 100644 index 000000000000..5fcb9ce7b0a4 --- /dev/null +++ b/src/components/AttachmentContext.ts @@ -0,0 +1,17 @@ +import {createContext} from 'react'; +import {ValueOf} from 'type-fest'; +import CONST from '@src/CONST'; + +type AttachmentContextProps = { + type?: ValueOf; + id?: string; +}; + +const AttachmentContext = createContext({ + type: undefined, + id: undefined, +}); + +AttachmentContext.displayName = 'AttachmentContext'; + +export {AttachmentContext}; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 79eaa30ee922..4e78f127d06c 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -2,6 +2,7 @@ import React, {memo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {CustomRendererProps, TBlock} from 'react-native-render-html'; +import {AttachmentContext} from '@components/AttachmentContext'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; @@ -78,19 +79,28 @@ function ImageRenderer({tnode}: ImageRendererProps) { ) : ( {({anchor, report, action, checkIfContextMenuActive}) => ( - { - const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report?.reportID ?? '', source); - Navigation.navigate(route); - }} - onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} - shouldUseHapticsOnLongPress - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} - accessibilityLabel={translate('accessibilityHints.viewAttachment')} - > - {thumbnailImageComponent} - + + {({id, type}) => ( + { + let route = ''; + if (source && type) { + if (id) { + route = ROUTES.ATTACHMENTS?.getRoute(id, type, source); + Navigation.navigate(route); + } + } + }} + onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + shouldUseHapticsOnLongPress + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} + > + {thumbnailImageComponent} + + )} + )} ); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx index 6802a4518ba1..cf8eebf81ac3 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx @@ -35,7 +35,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) { videoDimensions={{width, height}} videoDuration={duration} onShowModalPress={() => { - const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report?.reportID ?? '', sourceURL); + const route = ROUTES.ATTACHMENTS.getRoute(report?.reportID ?? '', CONST.ATTACHMENT_TYPE.REPORT, sourceURL); Navigation.navigate(route); }} /> diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index ffe4f8a9bea9..58052c971505 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -33,6 +33,7 @@ const SaveResponseInOnyx: Middleware = (requestResponse, request) => OnyxUpdates.saveUpdateInformation(responseToApply); // Ensure the queue is paused while the client resolves the gap in onyx updates so that updates are guaranteed to happen in a specific order. + console.log("**********************", {command: request.command}) return Promise.resolve({ ...response, shouldPauseQueue: true, diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 9157d7486c9e..a3fb36cb73ef 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -300,7 +300,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie getComponent={loadConciergePage} /> ); // Performance optimization to avoid context consumers to delay first render setTimeout(() => { diff --git a/src/libs/Navigation/dismissModal.ts b/src/libs/Navigation/dismissModal.ts index 4e159f0284bd..e09629d6f8ce 100644 --- a/src/libs/Navigation/dismissModal.ts +++ b/src/libs/Navigation/dismissModal.ts @@ -23,7 +23,7 @@ function dismissModal(navigationRef: NavigationContainerRef) case NAVIGATORS.RIGHT_MODAL_NAVIGATOR: case NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR: case SCREENS.NOT_FOUND: - case SCREENS.REPORT_ATTACHMENTS: + case SCREENS.ATTACHMENTS: case SCREENS.TRANSACTION_RECEIPT: case SCREENS.PROFILE_AVATAR: case SCREENS.WORKSPACE_AVATAR: diff --git a/src/libs/Navigation/dismissModalWithReport.ts b/src/libs/Navigation/dismissModalWithReport.ts index c0405c2c9da0..fe6fd288a2f9 100644 --- a/src/libs/Navigation/dismissModalWithReport.ts +++ b/src/libs/Navigation/dismissModalWithReport.ts @@ -37,7 +37,7 @@ function dismissModalWithReport(targetReport: Report | EmptyObject, navigationRe case NAVIGATORS.LEFT_MODAL_NAVIGATOR: case NAVIGATORS.RIGHT_MODAL_NAVIGATOR: case SCREENS.NOT_FOUND: - case SCREENS.REPORT_ATTACHMENTS: + case SCREENS.ATTACHMENTS: case SCREENS.TRANSACTION_RECEIPT: case SCREENS.PROFILE_AVATAR: case SCREENS.WORKSPACE_AVATAR: diff --git a/src/libs/Navigation/linkTo.ts b/src/libs/Navigation/linkTo.ts index 863cb102add4..e644127cac61 100644 --- a/src/libs/Navigation/linkTo.ts +++ b/src/libs/Navigation/linkTo.ts @@ -148,20 +148,23 @@ export default function linkTo(navigation: NavigationContainerRef['config'] = { [SCREENS.SIGN_IN_WITH_GOOGLE_DESKTOP]: ROUTES.GOOGLE_SIGN_IN, [SCREENS.SAML_SIGN_IN]: ROUTES.SAML_SIGN_IN, [SCREENS.DESKTOP_SIGN_IN_REDIRECT]: ROUTES.DESKTOP_SIGN_IN_REDIRECT, - [SCREENS.REPORT_ATTACHMENTS]: ROUTES.REPORT_ATTACHMENTS.route, + [SCREENS.ATTACHMENTS]: ROUTES.ATTACHMENTS.route, [SCREENS.PROFILE_AVATAR]: ROUTES.PROFILE_AVATAR.route, [SCREENS.WORKSPACE_AVATAR]: ROUTES.WORKSPACE_AVATAR.route, [SCREENS.REPORT_AVATAR]: ROUTES.REPORT_AVATAR.route, diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index f18637d8e52e..73fc0a328963 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -5,6 +5,7 @@ import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import getTopmostNestedRHPRoute from '@libs/Navigation/getTopmostNestedRHPRoute'; import type {BottomTabName, CentralPaneName, FullScreenName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types'; import {extractPolicyIDFromPath, getPathWithoutPolicyID} from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; import CENTRAL_PANE_TO_RHP_MAPPING from './CENTRAL_PANE_TO_RHP_MAPPING'; @@ -159,7 +160,8 @@ function getAdaptedState(state: PartialState const lhpNavigator = state.routes.find((route) => route.name === NAVIGATORS.LEFT_MODAL_NAVIGATOR); const onboardingModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR); const welcomeVideoModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.WELCOME_VIDEO_MODAL_NAVIGATOR); - const reportAttachmentsScreen = state.routes.find((route) => route.name === SCREENS.REPORT_ATTACHMENTS); + const attachmentsScreen = state.routes.find((route) => route.name === SCREENS.ATTACHMENTS); + console.log('9999999999', state.routes); if (isNarrowLayout) { metainfo.isFullScreenNavigatorMandatory = false; @@ -293,25 +295,27 @@ function getAdaptedState(state: PartialState metainfo, }; } - if (reportAttachmentsScreen) { + if (attachmentsScreen) { // Routes // - matching bottom tab // - central pane (report screen) of the attachment // - found report attachments const routes = []; - const reportAttachments = reportAttachmentsScreen as Route<'ReportAttachments', RootStackParamList['ReportAttachments']>; + const reportAttachments = attachmentsScreen as Route<'Attachments', RootStackParamList['Attachments']>; - const matchingBottomTabRoute = getMatchingBottomTabRouteForState(state); - routes.push(createBottomTabNavigator(matchingBottomTabRoute, policyID)); - if (!isNarrowLayout) { - routes.push(createCentralPaneNavigator({name: SCREENS.REPORT, params: {reportID: reportAttachments.params?.reportID ?? ''}})); - } - routes.push(reportAttachments); + if (reportAttachments.params?.type === CONST.ATTACHMENT_TYPE.REPORT) { + const matchingBottomTabRoute = getMatchingBottomTabRouteForState(state); + routes.push(createBottomTabNavigator(matchingBottomTabRoute, policyID)); + if (!isNarrowLayout) { + routes.push(createCentralPaneNavigator({name: SCREENS.REPORT, params: {reportID: reportAttachments.params?.id ?? ''}})); + } + routes.push(reportAttachments); - return { - adaptedState: getRoutesWithIndex(routes), - metainfo, - }; + return { + adaptedState: getRoutesWithIndex(routes), + metainfo, + }; + } } // We need to make sure that this if only handles states where we deeplink to the bottom tab directly diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index bc3d0bba637e..b0df6085aff8 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -767,9 +767,10 @@ type PublicScreensParamList = SharedScreensParamList & { type AuthScreensParamList = SharedScreensParamList & { [NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: NavigatorScreenParams; [SCREENS.CONCIERGE]: undefined; - [SCREENS.REPORT_ATTACHMENTS]: { - reportID: string; + [SCREENS.ATTACHMENTS]: { + id: string; source: string; + type: ValueOf; }; [SCREENS.PROFILE_AVATAR]: { accountID: string; diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx index 1893f81da2fe..f8121af32076 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx @@ -2,6 +2,7 @@ import React, {useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import {AttachmentContext} from '@components/AttachmentContext'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -29,6 +30,7 @@ type PrivateNotesListPageProps = WithReportAndPrivateNotesOrNotFoundProps & }; type NoteListItem = { + id: string; title: string; action: () => void; brickRoadIndicator: ValueOf | undefined; @@ -45,18 +47,20 @@ function PrivateNotesListPage({report, personalDetailsList, session}: PrivateNot */ function getMenuItem(item: NoteListItem) { return ( - + + + ); } diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 8d11744740bd..df303b763282 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -5,6 +5,7 @@ import {InteractionManager, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; +import {AttachmentContext} from '@components/AttachmentContext'; import Button from '@components/Button'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; @@ -373,6 +374,8 @@ function ReportActionItem({ [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport], ); + const attachmentContextValue = useMemo(() => ({id: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]); + const actionableItemButtons: ActionableItem[] = useMemo(() => { const isWhisperResolution = (action?.originalMessage as OriginalMessageActionableMentionWhisper['originalMessage'])?.resolution !== null; const isJoinChoice = (action?.originalMessage as OriginalMessageJoinPolicyChangeLog['originalMessage'])?.choice === ''; @@ -587,52 +590,56 @@ function ReportActionItem({ !ReportActionsUtils.isPendingRemove(action); children = ( - {draftMessage === undefined ? ( - - - {hasBeenFlagged && ( - - )} - {/** + + {isHidden ? translate('moderation.revealMessage') : translate('moderation.hideMessage')} + + + )} + {/** These are the actionable buttons that appear at the bottom of a Concierge message for example: Invite a user mentioned but not a member of the room https://github.com/Expensify/App/issues/32741 */} - {actionableItemButtons.length > 0 && ( - - )} - - ) : ( - - )} + {actionableItemButtons.length > 0 && ( + + )} + + ) : ( + + )} + ); } diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index 82b49d1e260c..e6dbd760e5af 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -6,13 +6,14 @@ import ComposerFocusManager from '@libs/ComposerFocusManager'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -type ReportAttachmentsProps = StackScreenProps; +type ReportAttachmentsProps = StackScreenProps; function ReportAttachments({route}: ReportAttachmentsProps) { - const reportID = route.params.reportID; + const reportID = route.params.id; const report = ReportUtils.getReport(reportID); // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource @@ -20,7 +21,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) { const onCarouselAttachmentChange = useCallback( (attachment: Attachment) => { - const routeToNavigate = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, String(attachment.source)); + const routeToNavigate = ROUTES.ATTACHMENTS.getRoute(reportID, CONST.ATTACHMENT_TYPE.REPORT, String(attachment.source)); Navigation.navigate(routeToNavigate); }, [reportID], From 68f91157f2db2f337a342ea6244151f4ad0cd90f Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 25 Apr 2024 12:35:11 +0700 Subject: [PATCH 02/13] fix navigation issue --- src/ROUTES.ts | 3 +- src/components/AttachmentContext.ts | 15 ++-- src/components/AttachmentModal.tsx | 11 +++ .../extractAttachmentsFromNote.ts | 80 +++++++++++++++++++ .../Attachments/AttachmentCarousel/index.tsx | 20 +++-- .../Attachments/AttachmentCarousel/types.ts | 8 ++ .../HTMLRenderers/ImageRenderer.tsx | 14 ++-- src/libs/Middleware/SaveResponseInOnyx.ts | 2 +- src/libs/Navigation/NavigationRoot.tsx | 2 +- src/libs/Navigation/linkTo.ts | 10 +-- src/libs/Navigation/linkingConfig/config.ts | 1 - .../linkingConfig/getAdaptedStateFromPath.ts | 1 - src/libs/Navigation/types.ts | 3 +- .../PrivateNotes/PrivateNotesListPage.tsx | 10 ++- src/pages/home/report/ReportActionItem.tsx | 2 +- src/pages/home/report/ReportAttachments.tsx | 11 ++- 16 files changed, 152 insertions(+), 41 deletions(-) create mode 100644 src/components/Attachments/AttachmentCarousel/extractAttachmentsFromNote.ts diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 47fe2516c7d7..2e65cee4ebb0 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -227,7 +227,8 @@ const ROUTES = { }, ATTACHMENTS: { route: 'attachment', - getRoute: (id: string, type: ValueOf, url: string) => `attachment?source=${encodeURIComponent(url)}&type=${type}${id ? `&id=${id}` : ''}`, + getRoute: (reportID: string, type: ValueOf, url: string, accountID?: number) => + `attachment?source=${encodeURIComponent(url)}&type=${type}${reportID ? `&reportID=${reportID}` : ''}${accountID ? `&accountID=${accountID}` : ''}`, }, REPORT_PARTICIPANTS: { route: 'r/:reportID/participants', diff --git a/src/components/AttachmentContext.ts b/src/components/AttachmentContext.ts index 5fcb9ce7b0a4..4ed6bdc9084f 100644 --- a/src/components/AttachmentContext.ts +++ b/src/components/AttachmentContext.ts @@ -1,17 +1,22 @@ import {createContext} from 'react'; -import {ValueOf} from 'type-fest'; -import CONST from '@src/CONST'; +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; type AttachmentContextProps = { type?: ValueOf; - id?: string; + reportID?: string; + accountID?: number; }; const AttachmentContext = createContext({ type: undefined, - id: undefined, + reportID: undefined, + accountID: undefined, }); AttachmentContext.displayName = 'AttachmentContext'; -export {AttachmentContext}; +export { + // eslint-disable-next-line import/prefer-default-export + AttachmentContext, +}; diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index 7d13524b78df..39a4ead98e15 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -5,6 +5,7 @@ import {GestureHandlerRootView} from 'react-native-gesture-handler'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import {useSharedValue} from 'react-native-reanimated'; +import type {ValueOf} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -106,6 +107,12 @@ type AttachmentModalProps = AttachmentModalOnyxProps & { /** The report that has this attachment */ report?: OnyxEntry | EmptyObject; + /** The type of the attachment */ + type?: ValueOf; + + /** If the attachment originates from a note, the accountID will represent the author of that note. */ + accountID?: number; + /** Optional callback to fire when we want to do something after modal show. */ onModalShow?: () => void; @@ -163,6 +170,8 @@ function AttachmentModal({ onModalClose = () => {}, isLoading = false, shouldShowNotFoundPage = false, + type = undefined, + accountID = undefined, }: AttachmentModalProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -524,6 +533,8 @@ function AttachmentModal({ )} {!isEmptyObject(report) && !isReceiptAttachment ? ( { + if (name === 'video') { + const source = tryResolveUrlFromApiRoot(attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); + if (uniqueSources.has(source)) { + return; + } + + uniqueSources.add(source); + const splittedUrl = attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE].split('/'); + attachments.unshift({ + source: tryResolveUrlFromApiRoot(attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]), + isAuthTokenRequired: Boolean(attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]), + file: {name: splittedUrl[splittedUrl.length - 1]}, + duration: Number(attribs[CONST.ATTACHMENT_DURATION_ATTRIBUTE]), + isReceipt: false, + hasBeenFlagged: false, + }); + return; + } + + if (name === 'img' && attribs.src) { + const expensifySource = attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]; + const source = tryResolveUrlFromApiRoot(expensifySource || attribs.src); + if (uniqueSources.has(source)) { + return; + } + + uniqueSources.add(source); + let fileName = attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${source}`); + + // Public image URLs might lack a file extension in the source URL, without an extension our + // AttachmentView fails to recognize them as images and renders fallback content instead. + // We apply this small hack to add an image extension and ensure AttachmentView renders the image. + const fileInfo = FileUtils.splitExtensionFromFileName(fileName); + if (!fileInfo.fileExtension) { + fileName = `${fileInfo.fileName || 'image'}.jpg`; + } + + // By iterating actions in chronological order and prepending each attachment + // we ensure correct order of attachments even across actions with multiple attachments. + attachments.unshift({ + reportActionID: attribs['data-id'], + source, + isAuthTokenRequired: Boolean(expensifySource), + file: {name: fileName}, + isReceipt: false, + hasBeenFlagged: attribs['data-flagged'] === 'true', + }); + } + }, + }); + + htmlParser.write(targetNote); + htmlParser.end(); + + return attachments.reverse(); +} + +export default extractAttachmentsFromNote; diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 3a7e0f19c4cd..64d694683b9b 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -19,6 +19,7 @@ import AttachmentCarouselCellRenderer from './AttachmentCarouselCellRenderer'; import CarouselActions from './CarouselActions'; import CarouselButtons from './CarouselButtons'; import CarouselItem from './CarouselItem'; +import extractAttachmentsFromNote from './extractAttachmentsFromNote'; import extractAttachmentsFromReport from './extractAttachmentsFromReport'; import type {AttachmentCaraouselOnyxProps, AttachmentCarouselProps, UpdatePageProps} from './types'; import useCarouselArrows from './useCarouselArrows'; @@ -29,7 +30,7 @@ const viewabilityConfig = { itemVisiblePercentThreshold: 95, }; -function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility}: AttachmentCarouselProps) { +function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, type, accountID}: AttachmentCarouselProps) { const theme = useTheme(); const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -49,19 +50,24 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, useEffect(() => { const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined; const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions ?? undefined); + let attachmentsFromNote: Attachment[] = []; + if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { + attachmentsFromNote = extractAttachmentsFromNote(report.reportID, accountID); + } + const targetAttachments = type === CONST.ATTACHMENT_TYPE.REPORT ? attachmentsFromReport : attachmentsFromNote; - if (isEqual(attachments, attachmentsFromReport)) { + if (isEqual(attachments, targetAttachments)) { return; } - const initialPage = attachmentsFromReport.findIndex(compareImage); + const initialPage = targetAttachments.findIndex(compareImage); // Dismiss the modal when deleting an attachment during its display in preview. if (initialPage === -1 && attachments.find(compareImage)) { Navigation.dismissModal(); } else { setPage(initialPage); - setAttachments(attachmentsFromReport); + setAttachments(targetAttachments); // Update the download button visibility in the parent modal if (setDownloadButtonVisibility) { @@ -69,11 +75,11 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, } // Update the parent modal's state with the source and name from the mapped attachments - if (attachmentsFromReport[initialPage] !== undefined && onNavigate) { - onNavigate(attachmentsFromReport[initialPage]); + if (targetAttachments[initialPage] !== undefined && onNavigate) { + onNavigate(targetAttachments[initialPage]); } } - }, [reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate]); + }, [reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate, accountID, report.reportID]); /** Updates the page state when the user navigates between attachments */ const updatePage = useCallback( diff --git a/src/components/Attachments/AttachmentCarousel/types.ts b/src/components/Attachments/AttachmentCarousel/types.ts index 8ba3489a5fcf..d31ebbd328cd 100644 --- a/src/components/Attachments/AttachmentCarousel/types.ts +++ b/src/components/Attachments/AttachmentCarousel/types.ts @@ -1,6 +1,8 @@ import type {ViewToken} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {Attachment, AttachmentSource} from '@components/Attachments/types'; +import type CONST from '@src/CONST'; import type {Report, ReportActions} from '@src/types/onyx'; type UpdatePageProps = { @@ -28,6 +30,12 @@ type AttachmentCarouselProps = AttachmentCaraouselOnyxProps & { /** The report currently being looked at */ report: Report; + /** The type of the attachment */ + type?: ValueOf; + + /** If the attachment originates from a note, the accountID will represent the author of that note. */ + accountID?: number; + /** A callback that is called when swipe-down-to-close gesture happens */ onClose: () => void; }; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 4e78f127d06c..36238d635d83 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -80,17 +80,19 @@ function ImageRenderer({tnode}: ImageRendererProps) { {({anchor, report, action, checkIfContextMenuActive}) => ( - {({id, type}) => ( + {({reportID, accountID, type}) => ( { + if (!source || !type) { + return; + } let route = ''; - if (source && type) { - if (id) { - route = ROUTES.ATTACHMENTS?.getRoute(id, type, source); - Navigation.navigate(route); - } + if (reportID) { + route = ROUTES.ATTACHMENTS?.getRoute(reportID, type, source, accountID); } + + Navigation.navigate(route); }} onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} shouldUseHapticsOnLongPress diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index 58052c971505..4f06f206eb37 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -33,7 +33,7 @@ const SaveResponseInOnyx: Middleware = (requestResponse, request) => OnyxUpdates.saveUpdateInformation(responseToApply); // Ensure the queue is paused while the client resolves the gap in onyx updates so that updates are guaranteed to happen in a specific order. - console.log("**********************", {command: request.command}) + return Promise.resolve({ ...response, shouldPauseQueue: true, diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 4c3084c5291a..f143d6b2bcc1 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -118,7 +118,7 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N if (!state) { return; } - console.log("777777777", state.routes) + const activeWorkspaceID = getPolicyIDFromState(state as NavigationState); // Performance optimization to avoid context consumers to delay first render setTimeout(() => { diff --git a/src/libs/Navigation/linkTo.ts b/src/libs/Navigation/linkTo.ts index e644127cac61..e08237c04601 100644 --- a/src/libs/Navigation/linkTo.ts +++ b/src/libs/Navigation/linkTo.ts @@ -148,23 +148,20 @@ export default function linkTo(navigation: NavigationContainerRef const onboardingModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR); const welcomeVideoModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.WELCOME_VIDEO_MODAL_NAVIGATOR); const attachmentsScreen = state.routes.find((route) => route.name === SCREENS.ATTACHMENTS); - console.log('9999999999', state.routes); if (isNarrowLayout) { metainfo.isFullScreenNavigatorMandatory = false; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b0df6085aff8..20e1be99d5a1 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -768,9 +768,10 @@ type AuthScreensParamList = SharedScreensParamList & { [NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: NavigatorScreenParams; [SCREENS.CONCIERGE]: undefined; [SCREENS.ATTACHMENTS]: { - id: string; + reportID: string; source: string; type: ValueOf; + accountID: string; }; [SCREENS.PROFILE_AVATAR]: { accountID: string; diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx index f8121af32076..286c91d628fd 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -30,24 +30,26 @@ type PrivateNotesListPageProps = WithReportAndPrivateNotesOrNotFoundProps & }; type NoteListItem = { - id: string; title: string; action: () => void; brickRoadIndicator: ValueOf | undefined; note: string; disabled: boolean; + reportID: string; + accountID: string; }; function PrivateNotesListPage({report, personalDetailsList, session}: PrivateNotesListPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const getAttachmentValue = useCallback((item: NoteListItem) => ({reportID: item.reportID, accountID: Number(item.accountID), type: CONST.ATTACHMENT_TYPE.NOTE}), []); /** * Gets the menu item for each workspace */ function getMenuItem(item: NoteListItem) { return ( - + { const privateNote = report.privateNotes?.[Number(accountID)]; return { + reportID: report.reportID, + accountID, title: Number(session?.accountID) === Number(accountID) ? translate('privateNotes.myNote') : personalDetailsList?.[accountID]?.login ?? '', action: () => Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, accountID)), brickRoadIndicator: privateNoteBrickRoadIndicator(Number(accountID)), diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index df303b763282..60b86c209c68 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -374,7 +374,7 @@ function ReportActionItem({ [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport], ); - const attachmentContextValue = useMemo(() => ({id: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]); + const attachmentContextValue = useMemo(() => ({reportID: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { const isWhisperResolution = (action?.originalMessage as OriginalMessageActionableMentionWhisper['originalMessage'])?.resolution !== null; diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index e6dbd760e5af..3ff472baee40 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -6,14 +6,15 @@ import ComposerFocusManager from '@libs/ComposerFocusManager'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; -import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type ReportAttachmentsProps = StackScreenProps; function ReportAttachments({route}: ReportAttachmentsProps) { - const reportID = route.params.id; + const reportID = route.params.reportID; + const type = route.params.type; + const accountID = route.params.accountID; const report = ReportUtils.getReport(reportID); // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource @@ -21,14 +22,16 @@ function ReportAttachments({route}: ReportAttachmentsProps) { const onCarouselAttachmentChange = useCallback( (attachment: Attachment) => { - const routeToNavigate = ROUTES.ATTACHMENTS.getRoute(reportID, CONST.ATTACHMENT_TYPE.REPORT, String(attachment.source)); + const routeToNavigate = ROUTES.ATTACHMENTS.getRoute(reportID, type, String(attachment.source), Number(accountID)); Navigation.navigate(routeToNavigate); }, - [reportID], + [reportID, accountID, type], ); return ( Date: Thu, 25 Apr 2024 12:36:21 +0700 Subject: [PATCH 03/13] fix remove unused change --- src/libs/Middleware/SaveResponseInOnyx.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index 4f06f206eb37..ffe4f8a9bea9 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -33,7 +33,6 @@ const SaveResponseInOnyx: Middleware = (requestResponse, request) => OnyxUpdates.saveUpdateInformation(responseToApply); // Ensure the queue is paused while the client resolves the gap in onyx updates so that updates are guaranteed to happen in a specific order. - return Promise.resolve({ ...response, shouldPauseQueue: true, From 07cb16f10664cdb345647401804793d455b2ef9a Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 25 Apr 2024 12:38:18 +0700 Subject: [PATCH 04/13] fix remove unused change --- src/libs/Navigation/NavigationRoot.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index f143d6b2bcc1..506eae2bdfd2 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -118,7 +118,6 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N if (!state) { return; } - const activeWorkspaceID = getPolicyIDFromState(state as NavigationState); // Performance optimization to avoid context consumers to delay first render setTimeout(() => { From 8d8867d24826a754f7abf6d1078dfb8a78c5f600 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 25 Apr 2024 14:32:11 +0700 Subject: [PATCH 05/13] fix remove unused change --- src/libs/Navigation/linkTo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/linkTo.ts b/src/libs/Navigation/linkTo.ts index e08237c04601..863cb102add4 100644 --- a/src/libs/Navigation/linkTo.ts +++ b/src/libs/Navigation/linkTo.ts @@ -154,6 +154,7 @@ export default function linkTo(navigation: NavigationContainerRef Date: Thu, 25 Apr 2024 14:42:53 +0700 Subject: [PATCH 06/13] fix typecheck --- src/ROUTES.ts | 2 +- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 7 +++---- .../Navigation/linkingConfig/getAdaptedStateFromPath.ts | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 249ba1c2571a..a1c609db6217 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -223,7 +223,7 @@ const ROUTES = { ATTACHMENTS: { route: 'attachment', getRoute: (reportID: string, type: ValueOf, url: string, accountID?: number) => - `attachment?source=${encodeURIComponent(url)}&type=${type}${reportID ? `&reportID=${reportID}` : ''}${accountID ? `&accountID=${accountID}` : ''}`, + `attachment?source=${encodeURIComponent(url)}&type=${type}${reportID ? `&reportID=${reportID}` : ''}${accountID ? `&accountID=${accountID}` : ''}` as const, }, REPORT_PARTICIPANTS: { route: 'r/:reportID/participants', diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 36238d635d83..221731bbeef6 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -87,12 +87,11 @@ function ImageRenderer({tnode}: ImageRendererProps) { if (!source || !type) { return; } - let route = ''; + if (reportID) { - route = ROUTES.ATTACHMENTS?.getRoute(reportID, type, source, accountID); + const route = ROUTES.ATTACHMENTS?.getRoute(reportID, type, source, accountID); + Navigation.navigate(route); } - - Navigation.navigate(route); }} onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} shouldUseHapticsOnLongPress diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index 4faa12696726..9a951bc4f9f2 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -306,7 +306,7 @@ function getAdaptedState(state: PartialState const matchingBottomTabRoute = getMatchingBottomTabRouteForState(state); routes.push(createBottomTabNavigator(matchingBottomTabRoute, policyID)); if (!isNarrowLayout) { - routes.push(createCentralPaneNavigator({name: SCREENS.REPORT, params: {reportID: reportAttachments.params?.id ?? ''}})); + routes.push(createCentralPaneNavigator({name: SCREENS.REPORT, params: {reportID: reportAttachments.params?.reportID ?? ''}})); } routes.push(reportAttachments); From c48b0aa66fccfbdf9e84733e8bdeb08a1b4a57f0 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 25 Apr 2024 14:49:50 +0700 Subject: [PATCH 07/13] fix lint --- src/components/Attachments/AttachmentCarousel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 64d694683b9b..6e8843472377 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -79,7 +79,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate(targetAttachments[initialPage]); } } - }, [reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate, accountID, report.reportID]); + }, [reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate, accountID, report.reportID, type]); /** Updates the page state when the user navigates between attachments */ const updatePage = useCallback( From aef525237d188ca52a7256b45bc2ece9f680f3d4 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 25 Apr 2024 15:39:59 +0700 Subject: [PATCH 08/13] fix native issue --- .../AttachmentCarousel/index.native.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.native.tsx b/src/components/Attachments/AttachmentCarousel/index.native.tsx index f6d63fc9307d..c97b16ca2988 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.native.tsx @@ -9,15 +9,17 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import CarouselButtons from './CarouselButtons'; +import extractAttachmentsFromNote from './extractAttachmentsFromNote'; import extractAttachmentsFromReport from './extractAttachmentsFromReport'; import type {AttachmentCarouselPagerHandle} from './Pager'; import AttachmentCarouselPager from './Pager'; import type {AttachmentCaraouselOnyxProps, AttachmentCarouselProps} from './types'; import useCarouselArrows from './useCarouselArrows'; -function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, onClose}: AttachmentCarouselProps) { +function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, onClose, type, accountID}: AttachmentCarouselProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const pagerRef = useRef(null); @@ -31,15 +33,19 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, useEffect(() => { const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined; const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions); - - const initialPage = attachmentsFromReport.findIndex(compareImage); + let attachmentsFromNote: Attachment[] = []; + if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { + attachmentsFromNote = extractAttachmentsFromNote(report.reportID, accountID); + } + const targetAttachments = type === CONST.ATTACHMENT_TYPE.REPORT ? attachmentsFromReport : attachmentsFromNote; + const initialPage = targetAttachments.findIndex(compareImage); // Dismiss the modal when deleting an attachment during its display in preview. if (initialPage === -1 && attachments.find(compareImage)) { Navigation.dismissModal(); } else { setPage(initialPage); - setAttachments(attachmentsFromReport); + setAttachments(targetAttachments); // Update the download button visibility in the parent modal if (setDownloadButtonVisibility) { @@ -47,8 +53,8 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, } // Update the parent modal's state with the source and name from the mapped attachments - if (attachmentsFromReport[initialPage] !== undefined && onNavigate) { - onNavigate(attachmentsFromReport[initialPage]); + if (targetAttachments[initialPage] !== undefined && onNavigate) { + onNavigate(targetAttachments[initialPage]); } } // eslint-disable-next-line react-hooks/exhaustive-deps From e59e1aba0139721239a59213e74af94c4355fdc4 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 30 Apr 2024 17:37:07 +0700 Subject: [PATCH 09/13] fix lint --- src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index 394c4e96bbea..939c64d53c84 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -166,7 +166,6 @@ function getAdaptedState(state: PartialState const welcomeVideoModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.WELCOME_VIDEO_MODAL_NAVIGATOR); const attachmentsScreen = state.routes.find((route) => route.name === SCREENS.ATTACHMENTS); const featureTrainingModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR); - const reportAttachmentsScreen = state.routes.find((route) => route.name === SCREENS.REPORT_ATTACHMENTS); if (isNarrowLayout) { metainfo.isFullScreenNavigatorMandatory = false; From 14589f8379fd04cca733338590e7328c1556ea4a Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 14 May 2024 04:59:16 +0700 Subject: [PATCH 10/13] fix create extractAttachments --- ...ntsFromReport.ts => extractAttachments.ts} | 21 ++++- .../extractAttachmentsFromNote.ts | 80 ------------------- .../AttachmentCarousel/index.native.tsx | 12 +-- .../Attachments/AttachmentCarousel/index.tsx | 12 +-- 4 files changed, 30 insertions(+), 95 deletions(-) rename src/components/Attachments/AttachmentCarousel/{extractAttachmentsFromReport.ts => extractAttachments.ts} (85%) delete mode 100644 src/components/Attachments/AttachmentCarousel/extractAttachmentsFromNote.ts diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.ts b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts similarity index 85% rename from src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.ts rename to src/components/Attachments/AttachmentCarousel/extractAttachments.ts index d1185f88ccd5..fcec07a327a0 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.ts +++ b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts @@ -1,8 +1,10 @@ import {Parser as HtmlParser} from 'htmlparser2'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {Attachment} from '@components/Attachments/types'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import {getReport} from '@libs/ReportUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import CONST from '@src/CONST'; import type {ReportAction, ReportActions} from '@src/types/onyx'; @@ -10,8 +12,13 @@ import type {ReportAction, ReportActions} from '@src/types/onyx'; /** * Constructs the initial component state from report actions */ -function extractAttachmentsFromReport(parentReportAction?: OnyxEntry, reportActions?: OnyxEntry) { - const actions = [...(parentReportAction ? [parentReportAction] : []), ...ReportActionsUtils.getSortedReportActions(Object.values(reportActions ?? {}))]; +function extractAttachments( + type: ValueOf, + {reportID, accountID, parentReportAction, reportActions}: {reportID?: string; accountID?: number; parentReportAction?: OnyxEntry; reportActions?: OnyxEntry}, +) { + const report = getReport(reportID); + const privateNotes = report?.privateNotes; + const targetNote = privateNotes?.[Number(accountID)]?.note ?? ''; const attachments: Attachment[] = []; // We handle duplicate image sources by considering the first instance as original. Selecting any duplicate @@ -71,6 +78,14 @@ function extractAttachmentsFromReport(parentReportAction?: OnyxEntry { if (!ReportActionsUtils.shouldReportActionBeVisible(action, key) || ReportActionsUtils.isMoneyRequestAction(action)) { return; @@ -86,4 +101,4 @@ function extractAttachmentsFromReport(parentReportAction?: OnyxEntry { - if (name === 'video') { - const source = tryResolveUrlFromApiRoot(attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); - if (uniqueSources.has(source)) { - return; - } - - uniqueSources.add(source); - const splittedUrl = attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE].split('/'); - attachments.unshift({ - source: tryResolveUrlFromApiRoot(attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]), - isAuthTokenRequired: Boolean(attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]), - file: {name: splittedUrl[splittedUrl.length - 1]}, - duration: Number(attribs[CONST.ATTACHMENT_DURATION_ATTRIBUTE]), - isReceipt: false, - hasBeenFlagged: false, - }); - return; - } - - if (name === 'img' && attribs.src) { - const expensifySource = attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]; - const source = tryResolveUrlFromApiRoot(expensifySource || attribs.src); - if (uniqueSources.has(source)) { - return; - } - - uniqueSources.add(source); - let fileName = attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${source}`); - - // Public image URLs might lack a file extension in the source URL, without an extension our - // AttachmentView fails to recognize them as images and renders fallback content instead. - // We apply this small hack to add an image extension and ensure AttachmentView renders the image. - const fileInfo = FileUtils.splitExtensionFromFileName(fileName); - if (!fileInfo.fileExtension) { - fileName = `${fileInfo.fileName || 'image'}.jpg`; - } - - // By iterating actions in chronological order and prepending each attachment - // we ensure correct order of attachments even across actions with multiple attachments. - attachments.unshift({ - reportActionID: attribs['data-id'], - source, - isAuthTokenRequired: Boolean(expensifySource), - file: {name: fileName}, - isReceipt: false, - hasBeenFlagged: attribs['data-flagged'] === 'true', - }); - } - }, - }); - - htmlParser.write(targetNote); - htmlParser.end(); - - return attachments.reverse(); -} - -export default extractAttachmentsFromNote; diff --git a/src/components/Attachments/AttachmentCarousel/index.native.tsx b/src/components/Attachments/AttachmentCarousel/index.native.tsx index c97b16ca2988..aad307073c0f 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.native.tsx @@ -12,8 +12,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import CarouselButtons from './CarouselButtons'; -import extractAttachmentsFromNote from './extractAttachmentsFromNote'; -import extractAttachmentsFromReport from './extractAttachmentsFromReport'; +import extractAttachments from './extractAttachments'; import type {AttachmentCarouselPagerHandle} from './Pager'; import AttachmentCarouselPager from './Pager'; import type {AttachmentCaraouselOnyxProps, AttachmentCarouselProps} from './types'; @@ -32,12 +31,13 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, useEffect(() => { const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined; - const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions); - let attachmentsFromNote: Attachment[] = []; + let targetAttachments: Attachment[] = []; if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { - attachmentsFromNote = extractAttachmentsFromNote(report.reportID, accountID); + targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {reportID: report.reportID, accountID}); + } else { + targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions}); } - const targetAttachments = type === CONST.ATTACHMENT_TYPE.REPORT ? attachmentsFromReport : attachmentsFromNote; + const initialPage = targetAttachments.findIndex(compareImage); // Dismiss the modal when deleting an attachment during its display in preview. diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 6e8843472377..e4d8df7bc02d 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -19,8 +19,7 @@ import AttachmentCarouselCellRenderer from './AttachmentCarouselCellRenderer'; import CarouselActions from './CarouselActions'; import CarouselButtons from './CarouselButtons'; import CarouselItem from './CarouselItem'; -import extractAttachmentsFromNote from './extractAttachmentsFromNote'; -import extractAttachmentsFromReport from './extractAttachmentsFromReport'; +import extractAttachments from './extractAttachments'; import type {AttachmentCaraouselOnyxProps, AttachmentCarouselProps, UpdatePageProps} from './types'; import useCarouselArrows from './useCarouselArrows'; @@ -49,12 +48,13 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, useEffect(() => { const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined; - const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions ?? undefined); - let attachmentsFromNote: Attachment[] = []; + let targetAttachments: Attachment[] = []; if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { - attachmentsFromNote = extractAttachmentsFromNote(report.reportID, accountID); + targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {reportID: report.reportID, accountID}); + } else { + targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions: reportActions ?? undefined}); } - const targetAttachments = type === CONST.ATTACHMENT_TYPE.REPORT ? attachmentsFromReport : attachmentsFromNote; + if (isEqual(attachments, targetAttachments)) { return; From ac97fb635f5897154ece8de10df879cfa9d24958 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 14 May 2024 05:11:42 +0700 Subject: [PATCH 11/13] fix lint --- src/components/Attachments/AttachmentCarousel/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index e4d8df7bc02d..520c054e7c70 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -54,7 +54,6 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, } else { targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions: reportActions ?? undefined}); } - if (isEqual(attachments, targetAttachments)) { return; From a4ab1d783d42fc0637c9e02e20feed02811e2151 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 15 May 2024 17:52:28 +0700 Subject: [PATCH 12/13] fix remove redundant attachment type --- src/CONST.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 9748c1435b38..fba3a434be80 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1185,9 +1185,7 @@ const CONST = { }, ATTACHMENT_TYPE: { REPORT: 'r', - REPORT_ACTION: 'a', NOTE: 'n', - SINGLE: 's', }, IMAGE_OBJECT_POSITION: { From bbaf3bc16718a44b81e75d80dc42ed1cf534bb74 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 30 May 2024 18:32:57 +0700 Subject: [PATCH 13/13] fix lint --- src/pages/PrivateNotes/PrivateNotesListPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx index 30367dd08be5..286c91d628fd 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx @@ -1,5 +1,4 @@ import React, {useCallback, useMemo} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest';