Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chat - File does not appear with strikethrough style when uploaded offline and deleted #47971

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import AttachmentView from '@components/Attachments/AttachmentView';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
Expand All @@ -13,26 +12,20 @@ import * as ReportUtils from '@libs/ReportUtils';
import * as Download from '@userActions/Download';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Download as OnyxDownload} from '@src/types/onyx';
import type AnchorForAttachmentsOnlyProps from './types';

type BaseAnchorForAttachmentsOnlyOnyxProps = {
/** If a file download is happening */
download: OnyxEntry<OnyxDownload>;
};

type BaseAnchorForAttachmentsOnlyProps = AnchorForAttachmentsOnlyProps &
BaseAnchorForAttachmentsOnlyOnyxProps & {
/** Press in handler for the link */
onPressIn?: () => void;
type BaseAnchorForAttachmentsOnlyProps = AnchorForAttachmentsOnlyProps & {
/** Press in handler for the link */
onPressIn?: () => void;

/** Press out handler for the link */
onPressOut?: () => void;
};
/** Press out handler for the link */
onPressOut?: () => void;
};

function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', download, onPressIn, onPressOut}: BaseAnchorForAttachmentsOnlyProps) {
function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', onPressIn, onPressOut, isDeleted}: BaseAnchorForAttachmentsOnlyProps) {
const sourceURLWithAuth = addEncryptedAuthTokenToURL(source);
const sourceID = (source.match(CONST.REGEX.ATTACHMENT_ID) ?? [])[1];
const [download] = useOnyx(`${ONYXKEYS.COLLECTION.DOWNLOAD}${sourceID}`);

const {isOffline} = useNetwork();
const styles = useThemeStyles();
Expand Down Expand Up @@ -69,6 +62,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow
shouldShowDownloadIcon={!!sourceID && !isOffline}
shouldShowLoadingSpinnerIcon={isDownloading}
isUsedAsChatAttachment
isDeleted={!!isDeleted}
/>
</PressableWithoutFeedback>
)}
Expand All @@ -78,11 +72,4 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow

BaseAnchorForAttachmentsOnly.displayName = 'BaseAnchorForAttachmentsOnly';

export default withOnyx<BaseAnchorForAttachmentsOnlyProps, BaseAnchorForAttachmentsOnlyOnyxProps>({
download: {
key: ({source}) => {
const sourceID = (source?.match(CONST.REGEX.ATTACHMENT_ID) ?? [])[1];
return `${ONYXKEYS.COLLECTION.DOWNLOAD}${sourceID}`;
},
},
})(BaseAnchorForAttachmentsOnly);
export default BaseAnchorForAttachmentsOnly;
3 changes: 3 additions & 0 deletions src/components/AnchorForAttachmentsOnly/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type AnchorForAttachmentsOnlyProps = {

/** Any additional styles to apply */
style?: StyleProp<ViewStyle>;

/** Whether the attachment is deleted */
isDeleted?: boolean;
};

export default AnchorForAttachmentsOnlyProps;
52 changes: 52 additions & 0 deletions src/components/AttachmentDeletedIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
import useNetwork from '@hooks/useNetwork';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';

type AttachmentDeletedIndicatorProps = {
/** Additional styles for container */
containerStyles?: StyleProp<ViewStyle>;
};

function AttachmentDeletedIndicator({containerStyles}: AttachmentDeletedIndicatorProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {isOffline} = useNetwork();

if (!isOffline) {
return null;
}

return (
<>
<View
style={[
styles.pAbsolute,
styles.alignItemsCenter,
styles.justifyContentCenter,
styles.highlightBG,
styles.deletedIndicatorOverlay,
styles.deletedAttachmentIndicator,
containerStyles,
]}
/>
<View style={[styles.pAbsolute, styles.deletedAttachmentIndicator, styles.alignItemsCenter, styles.justifyContentCenter, containerStyles]}>
<Icon
fill={theme.icon}
src={Expensicons.Trashcan}
width={variables.iconSizeSuperLarge}
height={variables.iconSizeSuperLarge}
/>
</View>
</>
);
}

AttachmentDeletedIndicator.displayName = 'AttachmentDeletedIndicator';

export default AttachmentDeletedIndicator;
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ type DefaultAttachmentViewProps = {
containerStyles?: StyleProp<ViewStyle>;

icon?: IconAsset;

/** Whether the attachment is deleted */
isDeleted?: boolean;
};

function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = false, shouldShowDownloadIcon, containerStyles, icon}: DefaultAttachmentViewProps) {
function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = false, shouldShowDownloadIcon, containerStyles, icon, isDeleted}: DefaultAttachmentViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
Expand All @@ -40,7 +43,7 @@ function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = fa
/>
</View>

<Text style={[styles.textStrong, styles.flexShrink1, styles.breakAll, styles.flexWrap, styles.mw100]}>{fileName}</Text>
<Text style={[styles.textStrong, styles.flexShrink1, styles.breakAll, styles.flexWrap, styles.mw100, isDeleted && styles.lineThrough]}>{fileName}</Text>
{!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && (
<Tooltip text={translate('common.download')}>
<View style={styles.ml2}>
Expand Down
81 changes: 37 additions & 44 deletions src/components/Attachments/AttachmentView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ 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 type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
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';
Expand All @@ -24,60 +23,57 @@ import * as TransactionUtils from '@libs/TransactionUtils';
import type {ColorValue} from '@styles/utils/types';
import variables from '@styles/variables';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Transaction} from '@src/types/onyx';
import AttachmentViewImage from './AttachmentViewImage';
import AttachmentViewPdf from './AttachmentViewPdf';
import AttachmentViewVideo from './AttachmentViewVideo';
import DefaultAttachmentView from './DefaultAttachmentView';
import HighResolutionInfo from './HighResolutionInfo';

type AttachmentViewOnyxProps = {
transaction: OnyxEntry<Transaction>;
};
type AttachmentViewProps = Attachment & {
/** Whether this view is the active screen */
isFocused?: boolean;

type AttachmentViewProps = AttachmentViewOnyxProps &
Attachment & {
/** Whether this view is the active screen */
isFocused?: boolean;
/** Function for handle on press */
onPress?: (e?: GestureResponderEvent | KeyboardEvent) => void;

/** Function for handle on press */
onPress?: (e?: GestureResponderEvent | KeyboardEvent) => void;
isUsedInAttachmentModal?: boolean;

isUsedInAttachmentModal?: boolean;
/** Flag to show/hide download icon */
shouldShowDownloadIcon?: boolean;

/** Flag to show/hide download icon */
shouldShowDownloadIcon?: boolean;
/** Flag to show the loading indicator */
shouldShowLoadingSpinnerIcon?: boolean;

/** Flag to show the loading indicator */
shouldShowLoadingSpinnerIcon?: boolean;
/** Notify parent that the UI should be modified to accommodate keyboard */
onToggleKeyboard?: (shouldFadeOut: boolean) => void;

/** Notify parent that the UI should be modified to accommodate keyboard */
onToggleKeyboard?: (shouldFadeOut: boolean) => void;
/** A callback when the PDF fails to load */
onPDFLoadError?: () => void;

/** A callback when the PDF fails to load */
onPDFLoadError?: () => void;
/** Extra styles to pass to View wrapper */
containerStyles?: StyleProp<ViewStyle>;

/** Extra styles to pass to View wrapper */
containerStyles?: StyleProp<ViewStyle>;
/** Denotes whether it is a workspace avatar or not */
isWorkspaceAvatar?: boolean;

/** Denotes whether it is a workspace avatar or not */
isWorkspaceAvatar?: boolean;
/** Denotes whether it is an icon (ex: SVG) */
maybeIcon?: boolean;

/** Denotes whether it is an icon (ex: SVG) */
maybeIcon?: boolean;
/** Fallback source to use in case of error */
fallbackSource?: AttachmentSource;

/** Fallback source to use in case of error */
fallbackSource?: AttachmentSource;
/* Whether it is hovered or not */
isHovered?: boolean;

/* Whether it is hovered or not */
isHovered?: boolean;
/** Whether the attachment is used as a chat attachment */
isUsedAsChatAttachment?: boolean;

/** Whether the attachment is used as a chat attachment */
isUsedAsChatAttachment?: boolean;
/* Flag indicating whether the attachment has been uploaded. */
isUploaded?: boolean;

/* Flag indicating whether the attachment has been uploaded. */
isUploaded?: boolean;
};
/** Whether the attachment is deleted */
isDeleted?: boolean;
};

function AttachmentView({
source,
Expand All @@ -95,13 +91,15 @@ function AttachmentView({
isWorkspaceAvatar,
maybeIcon,
fallbackSource,
transaction,
reportActionID,
isHovered,
duration,
isUsedAsChatAttachment,
isUploaded = true,
isDeleted,
transactionID,
}: AttachmentViewProps) {
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
const {translate} = useLocalize();
const {updateCurrentlyPlayingURL} = usePlaybackContext();
const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext);
Expand Down Expand Up @@ -290,18 +288,13 @@ function AttachmentView({
shouldShowDownloadIcon={shouldShowDownloadIcon}
shouldShowLoadingSpinnerIcon={shouldShowLoadingSpinnerIcon}
containerStyles={containerStyles}
isDeleted={isDeleted}
/>
);
}

AttachmentView.displayName = 'AttachmentView';

export default memo(
withOnyx<AttachmentViewProps, AttachmentViewOnyxProps>({
transaction: {
key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
},
})(AttachmentView),
);
export default memo(AttachmentView);

export type {AttachmentViewProps};
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) {
const isAttachment = !!htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE];
const tNodeChild = tnode?.domNode?.children?.at(0);
const displayName = tNodeChild && 'data' in tNodeChild && typeof tNodeChild.data === 'string' ? tNodeChild.data : '';
const parentStyle = tnode.parent?.styles?.nativeTextRet ?? {};
const attrHref = htmlAttribs.href || htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] || '';
const parentStyle = tnode.parent?.styles?.nativeTextRet ?? {};
const internalNewExpensifyPath = Link.getInternalNewExpensifyPath(attrHref);
const internalExpensifyPath = Link.getInternalExpensifyPath(attrHref);
const isVideo = attrHref && Str.isVideo(attrHref);

const isDeleted = HTMLEngineUtils.isDeletedNode(tnode);
const textDecorationLineStyle = isDeleted ? styles.underlineLineThrough : {};

if (!HTMLEngineUtils.isChildOfComment(tnode)) {
// This is not a comment from a chat, the AnchorForCommentsOnly uses a Pressable to create a context menu on right click.
// We don't have this behaviour in other links in NewDot
Expand All @@ -51,13 +54,11 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) {
<AnchorForAttachmentsOnly
source={tryResolveUrlFromApiRoot(attrHref)}
displayName={displayName}
isDeleted={isDeleted}
/>
);
}

const hasStrikethroughStyle = 'textDecorationLine' in parentStyle && parentStyle.textDecorationLine === 'line-through';
const textDecorationLineStyle = hasStrikethroughStyle ? styles.underlineLineThrough : {};

return (
<AnchorForCommentsOnly
href={attrHref}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {useOnyx} 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 {isDeletedNode} from '@components/HTMLEngineProvider/htmlEngineUtils';
import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus';
import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
Expand Down Expand Up @@ -32,6 +33,7 @@ function ImageRenderer({tnode}: ImageRendererProps) {
const {translate} = useLocalize();

const htmlAttribs = tnode.attributes;
const isDeleted = isDeletedNode(tnode);

// There are two kinds of images that need to be displayed:
//
Expand Down Expand Up @@ -72,6 +74,7 @@ function ImageRenderer({tnode}: ImageRendererProps) {
fallbackIcon={fallbackIcon}
imageWidth={imageWidth}
imageHeight={imageHeight}
isDeleted={isDeleted}
altText={alt}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
import {AttachmentContext} from '@components/AttachmentContext';
import {isDeletedNode} from '@components/HTMLEngineProvider/htmlEngineUtils';
import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
import VideoPlayerPreview from '@components/VideoPlayerPreview';
import useCurrentReportID from '@hooks/useCurrentReportID';
Expand All @@ -25,6 +26,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) {
const height = Number(htmlAttribs[CONST.ATTACHMENT_THUMBNAIL_HEIGHT_ATTRIBUTE]);
const duration = Number(htmlAttribs[CONST.ATTACHMENT_DURATION_ATTRIBUTE]);
const currentReportIDValue = useCurrentReportID();
const isDeleted = isDeletedNode(tnode);

return (
<ShowContextMenuContext.Consumer>
Expand All @@ -39,6 +41,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) {
thumbnailUrl={thumbnailUrl}
videoDimensions={{width, height}}
videoDuration={duration}
isDeleted={isDeleted}
onShowModalPress={() => {
if (!sourceURL || !type) {
return;
Expand Down
10 changes: 9 additions & 1 deletion src/components/HTMLEngineProvider/htmlEngineUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,12 @@ function isChildOfH1(tnode: TNode): boolean {
return isChildOfNode(tnode, (node) => node.domNode?.name !== undefined && node.domNode.name.toLowerCase() === 'h1');
}

export {computeEmbeddedMaxWidth, isChildOfComment, isCommentTag, isChildOfH1};
/**
* Check if the parent node has deleted style.
*/
function isDeletedNode(tnode: TNode): boolean {
const parentStyle = tnode.parent?.styles?.nativeTextRet ?? {};
return 'textDecorationLine' in parentStyle && parentStyle.textDecorationLine === 'line-through';
}

export {computeEmbeddedMaxWidth, isChildOfComment, isCommentTag, isChildOfH1, isDeletedNode};
Loading
Loading