Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a6f17d7
should work on some cases
sumo-slonik Aug 8, 2025
6093743
work in progress
sumo-slonik Aug 12, 2025
2c391e7
working moved logic
sumo-slonik Aug 12, 2025
298ecbb
fix pdf and other types files
sumo-slonik Aug 12, 2025
11b2e55
full working solution
sumo-slonik Aug 12, 2025
db7d30f
cleanUp and refactor
sumo-slonik Aug 12, 2025
c338df1
fix es lint
sumo-slonik Aug 12, 2025
9fa5134
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 13, 2025
83b6c77
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 13, 2025
1bd6fb5
changes after review
sumo-slonik Aug 13, 2025
14040ab
fix heic on native
sumo-slonik Aug 13, 2025
8675751
remove debugger
sumo-slonik Aug 13, 2025
2a33316
changes after code review
sumo-slonik Aug 14, 2025
3b121dd
eslint, spelling fixes
sumo-slonik Aug 14, 2025
a1b15cf
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 14, 2025
e3ddc92
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 14, 2025
01a0cee
rename variable
sumo-slonik Aug 19, 2025
a9284f7
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 27, 2025
bade1c6
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 27, 2025
73f1c4b
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 27, 2025
f7b8011
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 28, 2025
486f243
Merge branch 'main' into feature/kuba_nowakowski/fix_share_extension
sumo-slonik Aug 29, 2025
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
4 changes: 3 additions & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1854,7 +1854,7 @@ const CONST = {
ATTACHMENT_THUMBNAIL_WIDTH_ATTRIBUTE: 'data-expensify-width',
ATTACHMENT_THUMBNAIL_HEIGHT_ATTRIBUTE: 'data-expensify-height',
ATTACHMENT_DURATION_ATTRIBUTE: 'data-expensify-duration',

ATTACHMENT_IMAGE_DEFAULT_NAME: 'shared_image.png',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this btw? What scenario does the validatedFile?.uri not exist?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an error occurs during validation, or if we are not in a case where validation should be performed, this field will be empty. That’s why we need to guard against such cases.

ATTACHMENT_PICKER_TYPE: {
FILE: 'file',
IMAGE: 'image',
Expand Down Expand Up @@ -1893,6 +1893,7 @@ const CONST = {
MSWORD: 'application/msword',
ZIP: 'application/zip',
RFC822: 'message/rfc822',
HEIC: 'image/heic',
},

SHARE_FILE_MIMETYPE: {
Expand All @@ -1903,6 +1904,7 @@ const CONST = {
WEBP: 'image/webp',
TIF: 'image/tif',
TIFF: 'image/tiff',
HEIC: 'image/heic',
IMG: 'image/*',
PDF: 'application/pdf',
MSWORD: 'application/msword',
Expand Down
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ const ONYXKEYS = {
/** Temporary file to be shared from outside the app */
SHARE_TEMP_FILE: 'shareTempFile',

VALIDATED_FILE_OBJECT: 'shareFileObject',

/** Corpay fields to be used in the bank account creation setup */
CORPAY_FIELDS: 'corpayFields',

Expand Down Expand Up @@ -1220,6 +1222,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.CONCIERGE_REPORT_ID]: string;
[ONYXKEYS.SHARE_UNKNOWN_USER_DETAILS]: Participant;
[ONYXKEYS.SHARE_TEMP_FILE]: OnyxTypes.ShareTempFile;
[ONYXKEYS.VALIDATED_FILE_OBJECT]: OnyxTypes.FileObject | undefined;
[ONYXKEYS.CORPAY_FIELDS]: OnyxTypes.CorpayFields;
[ONYXKEYS.PRESERVED_USER_SESSION]: OnyxTypes.Session;
[ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING]: OnyxTypes.DismissedProductTraining;
Expand Down
22 changes: 13 additions & 9 deletions src/libs/ReceiptUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import findLast from 'lodash/findLast';
import type {OnyxEntry} from 'react-native-onyx';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Transaction} from '@src/types/onyx';
import type {ShareTempFile, Transaction} from '@src/types/onyx';
import type {ReceiptError, ReceiptSource} from '@src/types/onyx/Transaction';
import * as FileUtils from './fileDownload/FileUtils';
import * as TransactionUtils from './TransactionUtils';
import {isLocalFile as isLocalFileUtils, splitExtensionFromFileName} from './fileDownload/FileUtils';
import {hasReceipt, hasReceiptSource, isFetchingWaypointsFromServer} from './TransactionUtils';

type ThumbnailAndImageURI = {
image?: string;
Expand All @@ -27,10 +27,10 @@ type ThumbnailAndImageURI = {
* @param receiptFileName
*/
function getThumbnailAndImageURIs(transaction: OnyxEntry<Transaction>, receiptPath: ReceiptSource | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI {
if (!TransactionUtils.hasReceipt(transaction) && !receiptPath && !receiptFileName) {
if (!hasReceipt(transaction) && !receiptPath && !receiptFileName) {
return {isEmptyReceipt: true};
}
if (TransactionUtils.isFetchingWaypointsFromServer(transaction)) {
if (isFetchingWaypointsFromServer(transaction)) {
return {isThumbnail: true, isLocalFile: true};
}
// If there're errors, we need to display them in preview. We can store many files in errors, but we just need to get the last one
Expand All @@ -40,7 +40,7 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry<Transaction>, receiptPa
// filename of uploaded image or last part of remote URI
const filename = errors?.filename ?? transaction?.filename ?? receiptFileName ?? '';
const isReceiptImage = Str.isImage(filename);
const hasEReceipt = !TransactionUtils.hasReceiptSource(transaction) && transaction?.hasEReceipt;
const hasEReceipt = !hasReceiptSource(transaction) && transaction?.hasEReceipt;
const isReceiptPDF = Str.isPDF(filename);

if (hasEReceipt) {
Expand All @@ -60,11 +60,15 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry<Transaction>, receiptPa
return {thumbnail: `${path.substring(0, path.length - 4)}.jpg.1024.jpg`, image: path, filename};
}

const isLocalFile = FileUtils.isLocalFile(path);
const {fileExtension} = FileUtils.splitExtensionFromFileName(filename);
const isLocalFile = isLocalFileUtils(path);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we import this as isLocalFile as isLocalFileUtils

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

due to a naming conflict with a previously existing variable with the name isLocalFile

const {fileExtension} = splitExtensionFromFileName(filename);
return {isThumbnail: true, fileExtension: Object.values(CONST.IOU.FILE_TYPES).find((type) => type === fileExtension), image: path, isLocalFile, filename};
}

const shouldValidateFile = (file: ShareTempFile | undefined) => {
return file?.mimeType === CONST.SHARE_FILE_MIMETYPE.HEIC || file?.mimeType === CONST.SHARE_FILE_MIMETYPE.IMG;
};

// eslint-disable-next-line import/prefer-default-export
export {getThumbnailAndImageURIs};
export {getThumbnailAndImageURIs, shouldValidateFile};
export type {ThumbnailAndImageURI};
19 changes: 15 additions & 4 deletions src/libs/actions/Share.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Onyx from 'react-native-onyx';
import type {FileObject} from '@components/AttachmentModal';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ShareTempFile} from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';

/**
/**
Function for clearing old saved data before at the start of share-extension flow
*/
function clearShareData() {
Expand All @@ -13,7 +14,7 @@ function clearShareData() {
});
}

/**
/**
Function storing natively shared file's properties for processing across share-extension screens

function addTempShareFile(file: ShareTempFile) {
Expand All @@ -23,19 +24,29 @@ function addTempShareFile(file: ShareTempFile) {
Onyx.merge(ONYXKEYS.SHARE_TEMP_FILE, file);
}

/**
/**
* Stores a previously validated file object in Onyx for further use.
*
* @param file Array of validated file objects to be saved
*/
function addValidatedShareFile(file: FileObject[]) {
Onyx.set(ONYXKEYS.VALIDATED_FILE_OBJECT, file.at(0));
}

/**
Function storing selected user's details for the duration of share-extension flow, if account doesn't exist

* @param user selected user's details
*/
function saveUnknownUserDetails(user: Participant) {
Onyx.merge(ONYXKEYS.SHARE_UNKNOWN_USER_DETAILS, user);
}

/**
* Function to clear the unknown user details
*/
function clearUnknownUserDetails() {
Onyx.merge(ONYXKEYS.SHARE_UNKNOWN_USER_DETAILS, null);
}

export {addTempShareFile, saveUnknownUserDetails, clearShareData, clearUnknownUserDetails};
export {addTempShareFile, saveUnknownUserDetails, clearShareData, addValidatedShareFile, clearUnknownUserDetails};
28 changes: 17 additions & 11 deletions src/pages/Share/ShareDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {getFileName, readFileAsync} from '@libs/fileDownload/FileUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {ShareNavigatorParamList} from '@libs/Navigation/types';
import {getReportDisplayOption} from '@libs/OptionsListUtils';
import {shouldValidateFile} from '@libs/ReceiptUtils';
import {getReportOrDraftReport, isDraftReport} from '@libs/ReportUtils';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import variables from '@styles/variables';
Expand All @@ -47,15 +48,22 @@ function ShareDetailsPage({
const {translate} = useLocalize();
const [unknownUserDetails] = useOnyx(ONYXKEYS.SHARE_UNKNOWN_USER_DETAILS, {canBeMissing: true});
const [currentAttachment] = useOnyx(ONYXKEYS.SHARE_TEMP_FILE, {canBeMissing: true});
const [validatedFile] = useOnyx(ONYXKEYS.VALIDATED_FILE_OBJECT, {canBeMissing: true});

const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: (val) => val?.reports});
const isTextShared = currentAttachment?.mimeType === 'txt';
const isTextShared = currentAttachment?.mimeType === CONST.SHARE_FILE_MIMETYPE.TXT;
const shouldUsePreValidatedFile = shouldValidateFile(currentAttachment);
const [message, setMessage] = useState(isTextShared ? (currentAttachment?.content ?? '') : '');
const [errorTitle, setErrorTitle] = useState<string | undefined>(undefined);
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);

const report: OnyxEntry<ReportType> = getReportOrDraftReport(reportOrAccountID);
const displayReport = useMemo(() => getReportDisplayOption(report, unknownUserDetails, reportAttributesDerived), [report, unknownUserDetails, reportAttributesDerived]);

const fileSource = shouldUsePreValidatedFile ? (validatedFile?.uri ?? '') : (currentAttachment?.content ?? '');
const validateFileName = shouldUsePreValidatedFile ? getFileName(validatedFile?.uri ?? CONST.ATTACHMENT_IMAGE_DEFAULT_NAME) : getFileName(currentAttachment?.content ?? '');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The direct access of filename check caused this issue - #74542

const fileType = shouldUsePreValidatedFile ? (validatedFile?.type ?? CONST.SHARE_FILE_MIMETYPE.JPEG) : (currentAttachment?.mimeType ?? '');

useEffect(() => {
if (!currentAttachment?.content || errorTitle) {
return;
Expand Down Expand Up @@ -89,10 +97,8 @@ function ShareDetailsPage({
const currentUserID = getCurrentUserAccountID();
const shouldShowAttachment = !isTextShared;

const fileName = currentAttachment?.content.split('/').pop();

const handleShare = () => {
if (!currentAttachment) {
if (!currentAttachment || (shouldUsePreValidatedFile && !validatedFile)) {
return;
}

Expand All @@ -104,8 +110,8 @@ function ShareDetailsPage({
}

readFileAsync(
currentAttachment.content,
getFileName(currentAttachment.content),
fileSource,
validateFileName,
(file) => {
if (isDraft) {
openReport(
Expand All @@ -126,7 +132,7 @@ function ShareDetailsPage({
Navigation.navigate(routeToNavigate, {forceReplace: true});
},
() => {},
currentAttachment.mimeType,
fileType,
);
};

Expand Down Expand Up @@ -196,14 +202,14 @@ function ShareDetailsPage({
</View>
<SafeAreaView>
<AttachmentModal
headerTitle={fileName}
source={currentAttachment?.content}
originalFileName={fileName}
headerTitle={validateFileName}
source={fileSource}
originalFileName={validateFileName}
fallbackSource={FallbackAvatar}
>
{({show}) => (
<AttachmentPreview
source={currentAttachment?.content ?? ''}
source={fileSource ?? ''}
aspectRatio={currentAttachment?.aspectRatio}
onPress={show}
onLoadError={() => {
Expand Down
26 changes: 25 additions & 1 deletion src/pages/Share/ShareRootPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
import ScreenWrapper from '@components/ScreenWrapper';
import TabNavigatorSkeleton from '@components/Skeletons/TabNavigatorSkeleton';
import TabSelector from '@components/TabSelector/TabSelector';
import useFilesValidation from '@hooks/useFilesValidation';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import {addTempShareFile, clearShareData} from '@libs/actions/Share';
import {addTempShareFile, addValidatedShareFile, clearShareData} from '@libs/actions/Share';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {splitExtensionFromFileName, validateImageForCorruption} from '@libs/fileDownload/FileUtils';
import Navigation from '@libs/Navigation/Navigation';
import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator';
import {shouldValidateFile} from '@libs/ReceiptUtils';
import ShareActionHandler from '@libs/ShareActionHandlerModule';
import type {FileObject} from '@pages/media/AttachmentModalScreen/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {ShareTempFile} from '@src/types/onyx';
import getFileSize from './getFileSize';
Expand All @@ -33,6 +37,25 @@
}

function ShareRootPage() {
const [currentAttachment] = useOnyx(ONYXKEYS.SHARE_TEMP_FILE, {canBeMissing: true});

const {validateFiles} = useFilesValidation(addValidatedShareFile);
const isTextShared = currentAttachment?.mimeType === 'txt';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isTextShared = currentAttachment?.mimeType === 'txt';
const isTextShared = currentAttachment?.mimeType === CONST.SHARE_FILE_MIMETYPE.TXT;


const validateFileIfNecessary = (file: ShareTempFile) => {
if (!file || isTextShared || !shouldValidateFile(file)) {
return;
}

validateFiles([
{
name: file.id,
uri: file.content,
type: file.mimeType,
},
]);
};

const appState = useRef(AppState.currentState);
const [isFileReady, setIsFileReady] = useState(false);

Expand Down Expand Up @@ -95,13 +118,14 @@
} else {
setIsFileScannable(false);
}
validateFileIfNecessary(tempFile);
setIsFileReady(true);
}

addTempShareFile(tempFile);
}
});
}, [receiptFileFormats, shareFileMimeTypes, translate, errorTitle]);

Check warning on line 128 in src/pages/Share/ShareRootPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

React Hook useCallback has a missing dependency: 'validateFileIfNecessary'. Either include it or remove the dependency array

useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
Expand Down
23 changes: 17 additions & 6 deletions src/pages/Share/SubmitDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import navigateAfterInteraction from '@libs/Navigation/navigateAfterInteraction'
import Navigation from '@libs/Navigation/Navigation';
import type {ShareNavigatorParamList} from '@libs/Navigation/types';
import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
import {shouldValidateFile} from '@libs/ReceiptUtils';
import {getReportOrDraftReport, isSelfDM} from '@libs/ReportUtils';
import {getDefaultTaxCode} from '@libs/TransactionUtils';
import CONST from '@src/CONST';
Expand All @@ -40,7 +41,6 @@ function SubmitDetailsPage({
}: ShareDetailsPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [currentAttachment] = useOnyx(ONYXKEYS.SHARE_TEMP_FILE, {canBeMissing: true});
const [unknownUserDetails] = useOnyx(ONYXKEYS.SHARE_UNKNOWN_USER_DETAILS, {canBeMissing: true});
const [personalDetails] = useOnyx(`${ONYXKEYS.PERSONAL_DETAILS_LIST}`, {canBeMissing: true});
const report: OnyxEntry<ReportType> = getReportOrDraftReport(reportOrAccountID);
Expand All @@ -53,6 +53,10 @@ function SubmitDetailsPage({
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: (val) => val?.reports});
const [currentDate] = useOnyx(ONYXKEYS.CURRENT_DATE, {canBeMissing: true});
const [validFilesToUpload] = useOnyx(ONYXKEYS.VALIDATED_FILE_OBJECT, {canBeMissing: true});
const [currentAttachment] = useOnyx(ONYXKEYS.SHARE_TEMP_FILE, {canBeMissing: true});
const shouldUsePreValidatedFile = shouldValidateFile(currentAttachment);

const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false);

Expand All @@ -62,6 +66,10 @@ function SubmitDetailsPage({
const {isBetaEnabled} = usePermissions();
const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS) || !account?.shouldBlockTransactionThreadReportCreation;

const fileUri = shouldUsePreValidatedFile ? (validFilesToUpload?.uri ?? '') : (currentAttachment?.content ?? '');
const fileName = shouldUsePreValidatedFile ? getFileName(validFilesToUpload?.uri ?? CONST.ATTACHMENT_IMAGE_DEFAULT_NAME) : getFileName(currentAttachment?.content ?? '');
const fileType = shouldUsePreValidatedFile ? (validFilesToUpload?.type ?? CONST.RECEIPT_ALLOWED_FILE_TYPES.JPEG) : (currentAttachment?.mimeType ?? '');

useEffect(() => {
if (!errorTitle || !errorMessage) {
return;
Expand Down Expand Up @@ -193,13 +201,16 @@ function SubmitDetailsPage({
setStartLocationPermissionFlow(true);
return;
}
if (!currentAttachment) {
return;
}

readFileAsync(
currentAttachment?.content ?? '',
getFileName(currentAttachment?.content ?? 'shared_image.png'),
fileUri,
fileName,
(file) => onSuccess(file, shouldStartLocationPermissionFlow),
() => {},
currentAttachment?.mimeType ?? 'image/jpeg',
fileType,
);
};

Expand Down Expand Up @@ -230,8 +241,8 @@ function SubmitDetailsPage({
iouComment={trimmedComment}
iouCategory={transaction?.category}
onConfirm={() => onConfirm(true)}
receiptPath={currentAttachment?.content}
receiptFilename={getFileName(currentAttachment?.content ?? '')}
receiptPath={fileUri}
receiptFilename={getFileName(fileName)}
reportID={reportOrAccountID}
shouldShowSmartScanFields={false}
isDistanceRequest={false}
Expand Down
2 changes: 2 additions & 0 deletions src/types/onyx/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {FileObject} from '@components/AttachmentModal';
import type {OnboardingPurpose} from '@libs/actions/Welcome/OnboardingFlow';
import type Account from './Account';
import type AccountData from './AccountData';
Expand Down Expand Up @@ -125,6 +126,7 @@ import type WalletTerms from './WalletTerms';
import type WalletTransfer from './WalletTransfer';

export type {
FileObject,
TryNewDot,
Account,
AccountData,
Expand Down
Loading