Skip to content

Commit

Permalink
Merge pull request Expensify#23143 from ahmedGaber93/issue-22261
Browse files Browse the repository at this point in the history
display the not found page when attachment is deleted.
  • Loading branch information
marcochavezf authored Aug 17, 2023
2 parents ae96194 + a0962d1 commit f45ddbe
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 123 deletions.
17 changes: 15 additions & 2 deletions src/components/AttachmentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import HeaderGap from './HeaderGap';
import SafeAreaConsumer from './SafeAreaConsumer';
import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL';
import reportPropTypes from '../pages/reportPropTypes';
import tryResolveUrlFromApiRoot from '../libs/tryResolveUrlFromApiRoot';

/**
* Modal render prop component that exposes modal launching triggers that can be used
Expand Down Expand Up @@ -103,6 +104,7 @@ function AttachmentModal(props) {
const [modalType, setModalType] = useState(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE);
const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false);
const [confirmButtonFadeAnimation] = useState(new Animated.Value(1));
const [shouldShowDownloadButton, setShouldShowDownloadButton] = React.useState(true);
const [file, setFile] = useState(
props.originalFileName
? {
Expand Down Expand Up @@ -142,6 +144,16 @@ function AttachmentModal(props) {
[translate],
);

const setDownloadButtonVisibility = useCallback(
(shouldShowButton) => {
if (shouldShowDownloadButton === shouldShowButton) {
return;
}
setShouldShowDownloadButton(shouldShowButton);
},
[shouldShowDownloadButton],
);

/**
* Download the currently viewed attachment.
*/
Expand Down Expand Up @@ -324,7 +336,7 @@ function AttachmentModal(props) {
<HeaderWithBackButton
title={props.headerTitle || translate('common.attachment')}
shouldShowBorderBottom
shouldShowDownloadButton={props.allowDownload}
shouldShowDownloadButton={props.allowDownload && shouldShowDownloadButton}
onDownloadButtonPress={() => downloadAttachment(source)}
shouldShowCloseButton={!props.isSmallScreenWidth}
shouldShowBackButton={props.isSmallScreenWidth}
Expand All @@ -336,9 +348,10 @@ function AttachmentModal(props) {
<AttachmentCarousel
report={props.report}
onNavigate={onNavigate}
source={props.source}
source={tryResolveUrlFromApiRoot(props.source)}
onClose={closeModal}
onToggleKeyboard={updateConfirmButtonVisibility}
setDownloadButtonVisibility={setDownloadButtonVisibility}
/>
) : (
Boolean(sourceForAttachmentView) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const propTypes = {
/** Callback to close carousel when user swipes down (on native) */
onClose: PropTypes.func,

/** Function to change the download button Visibility */
setDownloadButtonVisibility: PropTypes.func,

/** Object of report actions for this report */
reportActions: PropTypes.shape(reportActionPropTypes),

Expand All @@ -24,6 +27,7 @@ const defaultProps = {
reportActions: {},
onNavigate: () => {},
onClose: () => {},
setDownloadButtonVisibility: () => {},
};

export {propTypes, defaultProps};
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@ import _ from 'underscore';
import * as ReportActionsUtils from '../../../libs/ReportActionsUtils';
import CONST from '../../../CONST';
import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot';
import Navigation from '../../../libs/Navigation/Navigation';

/**
* Constructs the initial component state from report actions
* @param {Object} report
* @param {Array} reportActions
* @param {String} source
* @returns {{attachments: Array, initialPage: Number, initialItem: Object, initialActiveSource: String}}
* @returns {Array}
*/
function extractAttachmentsFromReport(report, reportActions, source) {
function extractAttachmentsFromReport(report, reportActions) {
const actions = [ReportActionsUtils.getParentReportAction(report), ...ReportActionsUtils.getSortedReportActions(_.values(reportActions))];
let attachments = [];
const attachments = [];

const htmlParser = new HtmlParser({
onopentag: (name, attribs) => {
Expand Down Expand Up @@ -42,27 +40,7 @@ function extractAttachmentsFromReport(report, reportActions, source) {
});
htmlParser.end();

attachments = attachments.reverse();

const initialPage = _.findIndex(attachments, (a) => a.source === source);
if (initialPage === -1) {
Navigation.dismissModal();
return {
attachments: [],
initialPage: 0,
initialItem: undefined,
initialActiveSource: null,
};
}

const initialItem = attachments[initialPage];

return {
attachments,
initialPage,
initialItem,
initialActiveSource: initialItem.source,
};
return attachments.reverse();
}

export default extractAttachmentsFromReport;
134 changes: 81 additions & 53 deletions src/components/Attachments/AttachmentCarousel/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useRef, useCallback, useState, useEffect, useMemo} from 'react';
import React, {useRef, useCallback, useState, useEffect} from 'react';
import {View, FlatList, PixelRatio, Keyboard} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
Expand All @@ -15,6 +15,10 @@ import withLocalize from '../../withLocalize';
import compose from '../../../libs/compose';
import useCarouselArrows from './useCarouselArrows';
import useWindowDimensions from '../../../hooks/useWindowDimensions';
import Navigation from '../../../libs/Navigation/Navigation';
import BlockingView from '../../BlockingViews/BlockingView';
import * as Illustrations from '../../Icon/Illustrations';
import variables from '../../../styles/variables';

const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
const viewabilityConfig = {
Expand All @@ -23,24 +27,37 @@ const viewabilityConfig = {
itemVisiblePercentThreshold: 95,
};

function AttachmentCarousel({report, reportActions, source, onNavigate}) {
function AttachmentCarousel({report, reportActions, source, onNavigate, setDownloadButtonVisibility, translate}) {
const scrollRef = useRef(null);

const {windowWidth, isSmallScreenWidth} = useWindowDimensions();

const {attachments, initialPage, initialActiveSource, initialItem} = useMemo(() => extractAttachmentsFromReport(report, reportActions, source), [report, reportActions, source]);
const [containerWidth, setContainerWidth] = useState(0);
const [page, setPage] = useState(0);
const [attachments, setAttachments] = useState([]);
const [activeSource, setActiveSource] = useState(source);
const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows();

useEffect(() => {
// Update the parent modal's state with the source and name from the mapped attachments
if (!initialItem) return;
onNavigate(initialItem);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialItem]);
const attachmentsFromReport = extractAttachmentsFromReport(report, reportActions);

const [containerWidth, setContainerWidth] = useState(0);
const [page, setPage] = useState(initialPage);
const [activeSource, setActiveSource] = useState(initialActiveSource);
const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows();
const initialPage = _.findIndex(attachmentsFromReport, (a) => a.source === source);

// Dismiss the modal when deleting an attachment during its display in preview.
if (initialPage === -1 && _.find(attachments, (a) => a.source === source)) {
Navigation.dismissModal();
} else {
setPage(initialPage);
setAttachments(attachmentsFromReport);

// Update the download button visibility in the parent modal
setDownloadButtonVisibility(initialPage !== -1);

// Update the parent modal's state with the source and name from the mapped attachments
if (!_.isUndefined(attachmentsFromReport[initialPage])) onNavigate(attachmentsFromReport[initialPage]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [report, reportActions, source]);

/**
* Updates the page state when the user navigates between attachments
Expand Down Expand Up @@ -153,49 +170,60 @@ function AttachmentCarousel({report, reportActions, source, onNavigate}) {
onMouseEnter={() => !canUseTouchScreen && setShouldShowArrows(true)}
onMouseLeave={() => !canUseTouchScreen && setShouldShowArrows(false)}
>
<CarouselButtons
shouldShowArrows={shouldShowArrows}
page={page}
attachments={attachments}
onBack={() => cycleThroughAttachments(-1)}
onForward={() => cycleThroughAttachments(1)}
autoHideArrow={autoHideArrows}
cancelAutoHideArrow={cancelAutoHideArrows}
/>

{containerWidth > 0 && (
<FlatList
keyboardShouldPersistTaps="handled"
listKey="AttachmentCarousel"
horizontal
decelerationRate="fast"
showsHorizontalScrollIndicator={false}
bounces={false}
// Scroll only one image at a time no matter how fast the user swipes
disableIntervalMomentum
pagingEnabled
snapToAlignment="start"
snapToInterval={containerWidth}
// Enable scrolling by swiping on mobile (touch) devices only
// disable scroll for desktop/browsers because they add their scrollbars
// Enable scrolling FlatList only when PDF is not in a zoomed state
scrollEnabled={canUseTouchScreen}
ref={scrollRef}
initialScrollIndex={page}
initialNumToRender={3}
windowSize={5}
maxToRenderPerBatch={3}
data={attachments}
CellRendererComponent={renderCell}
renderItem={renderItem}
getItemLayout={getItemLayout}
keyExtractor={(item) => item.source}
viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={updatePage.current}
{page === -1 ? (
<BlockingView
icon={Illustrations.ToddBehindCloud}
iconWidth={variables.modalTopIconWidth}
iconHeight={variables.modalTopIconHeight}
title={translate('notFound.notHere')}
/>
) : (
<>
<CarouselButtons
shouldShowArrows={shouldShowArrows}
page={page}
attachments={attachments}
onBack={() => cycleThroughAttachments(-1)}
onForward={() => cycleThroughAttachments(1)}
autoHideArrow={autoHideArrows}
cancelAutoHideArrow={cancelAutoHideArrows}
/>

{containerWidth > 0 && (
<FlatList
keyboardShouldPersistTaps="handled"
listKey="AttachmentCarousel"
horizontal
decelerationRate="fast"
showsHorizontalScrollIndicator={false}
bounces={false}
// Scroll only one image at a time no matter how fast the user swipes
disableIntervalMomentum
pagingEnabled
snapToAlignment="start"
snapToInterval={containerWidth}
// Enable scrolling by swiping on mobile (touch) devices only
// disable scroll for desktop/browsers because they add their scrollbars
// Enable scrolling FlatList only when PDF is not in a zoomed state
scrollEnabled={canUseTouchScreen}
ref={scrollRef}
initialScrollIndex={page}
initialNumToRender={3}
windowSize={5}
maxToRenderPerBatch={3}
data={attachments}
CellRendererComponent={renderCell}
renderItem={renderItem}
getItemLayout={getItemLayout}
keyExtractor={(item) => item.source}
viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={updatePage.current}
/>
)}

<CarouselActions onCycleThroughAttachments={cycleThroughAttachments} />
</>
)}

<CarouselActions onCycleThroughAttachments={cycleThroughAttachments} />
</View>
);
}
Expand Down
Loading

0 comments on commit f45ddbe

Please sign in to comment.