diff --git a/assets/images/camera-flip.svg b/assets/images/camera-flip.svg
deleted file mode 100644
index 6d05251e0c77..000000000000
--- a/assets/images/camera-flip.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/jest/setup.ts b/jest/setup.ts
index ea2a0d44086e..ee2b1702ba1e 100644
--- a/jest/setup.ts
+++ b/jest/setup.ts
@@ -375,10 +375,3 @@ jest.mock('@src/hooks/useDomainDocumentTitle', () => ({
__esModule: true,
default: jest.fn(),
}));
-
-jest.mock('react-native-vision-camera', () => ({
- Camera: 'Camera',
- useCameraDevice: jest.fn(() => null),
- useCameraFormat: jest.fn(() => null),
- useCameraPermission: jest.fn(() => ({hasPermission: false, requestPermission: jest.fn()})),
-}));
diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index e13ba87a9aa9..4a9087ed3c21 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -1271,7 +1271,6 @@ const CONST = {
SHUTTER_SIZE: 90,
MAX_REPORT_PREVIEW_RECEIPTS: 3,
FLASH_DELAY_MS: 2000,
- PHOTO_ASPECT_RATIO: 4 / 3,
},
RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN: 120,
REPORT: {
diff --git a/src/components/AttachmentPicker/AttachmentCamera.tsx b/src/components/AttachmentPicker/AttachmentCamera.tsx
deleted file mode 100644
index 080407d17691..000000000000
--- a/src/components/AttachmentPicker/AttachmentCamera.tsx
+++ /dev/null
@@ -1,253 +0,0 @@
-import React, {useCallback, useEffect, useRef, useState} from 'react';
-import {Alert, Modal, View} from 'react-native';
-import {RESULTS} from 'react-native-permissions';
-import type {Camera, PhotoFile} from 'react-native-vision-camera';
-import {useCameraDevice, useCameraFormat, Camera as VisionCamera} from 'react-native-vision-camera';
-import ActivityIndicator from '@components/ActivityIndicator';
-import Button from '@components/Button';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import Icon from '@components/Icon';
-import ImageSVG from '@components/ImageSVG';
-import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
-import Text from '@components/Text';
-import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
-import useLocalize from '@hooks/useLocalize';
-import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
-import useStyleUtils from '@hooks/useStyleUtils';
-import useTheme from '@hooks/useTheme';
-import useThemeStyles from '@hooks/useThemeStyles';
-import {showCameraPermissionsAlert} from '@libs/fileDownload/FileUtils';
-import getPhotoSource from '@libs/fileDownload/getPhotoSource';
-import Log from '@libs/Log';
-import CameraPermission from '@pages/iou/request/step/IOURequestStepScan/CameraPermission';
-import CONST from '@src/CONST';
-
-type CapturedPhoto = {
- uri: string;
- fileName: string;
- type: string;
- width: number;
- height: number;
-};
-
-type AttachmentCameraProps = {
- /** Whether the camera modal is visible */
- isVisible: boolean;
-
- /** Callback when a photo is captured */
- onCapture: (photos: CapturedPhoto[]) => void;
-
- /** Callback when the camera is closed without capturing */
- onClose: () => void;
-};
-
-function AttachmentCamera({isVisible, onCapture, onClose}: AttachmentCameraProps) {
- const theme = useTheme();
- const styles = useThemeStyles();
- const StyleUtils = useStyleUtils();
- const {translate} = useLocalize();
- const insets = useSafeAreaInsets();
-
- const lazyIcons = useMemoizedLazyExpensifyIcons(['Bolt', 'boltSlash', 'CameraFlip']);
- const lazyIllustrations = useMemoizedLazyIllustrations(['Shutter', 'Hand']);
-
- const camera = useRef(null);
- const [cameraPermissionStatus, setCameraPermissionStatus] = useState(null);
- const isCapturing = useRef(false);
- const [cameraPosition, setCameraPosition] = useState<'back' | 'front'>('back');
-
- const device = useCameraDevice(cameraPosition, {
- physicalDevices: ['wide-angle-camera', 'ultra-wide-angle-camera'],
- });
- const format = useCameraFormat(device, [{photoAspectRatio: CONST.RECEIPT.PHOTO_ASPECT_RATIO}, {photoResolution: 'max'}]);
- const cameraAspectRatio = format ? format.photoHeight / format.photoWidth : undefined;
- const hasFlash = !!device?.hasFlash;
-
- // Request camera permissions when modal opens
- useEffect(() => {
- if (!isVisible) {
- return;
- }
-
- CameraPermission.getCameraPermissionStatus?.()
- .then((status) => {
- if (status === RESULTS.DENIED) {
- return CameraPermission.requestCameraPermission?.().then(setCameraPermissionStatus);
- }
- setCameraPermissionStatus(status);
- })
- .catch(() => setCameraPermissionStatus(RESULTS.UNAVAILABLE));
- }, [isVisible]);
-
- const [flash, setFlash] = useState(false);
-
- const askForPermissions = useCallback(() => {
- CameraPermission.requestCameraPermission?.()
- .then((status: string) => {
- setCameraPermissionStatus(status);
- if (status === RESULTS.BLOCKED) {
- showCameraPermissionsAlert(translate);
- }
- })
- .catch(() => setCameraPermissionStatus(RESULTS.UNAVAILABLE));
- }, [translate]);
-
- const capturePhoto = useCallback(() => {
- if (!camera.current || isCapturing.current) {
- return;
- }
-
- if (cameraPermissionStatus === RESULTS.DENIED || cameraPermissionStatus === RESULTS.BLOCKED) {
- askForPermissions();
- return;
- }
-
- isCapturing.current = true;
-
- camera.current
- .takePhoto({
- flash: flash && hasFlash ? 'on' : 'off',
- })
- .then((photo: PhotoFile) => {
- const uri = getPhotoSource(photo.path);
- const fileName = photo.path.split('/').pop() ?? `photo_${Date.now()}.jpg`;
-
- onCapture([
- {
- uri,
- fileName,
- type: 'image/jpeg',
- width: photo.width,
- height: photo.height,
- },
- ]);
- })
- .catch((error: unknown) => {
- Log.warn('AttachmentCamera: Error taking photo', {error});
- Alert.alert(translate('receipt.cameraErrorTitle'), translate('receipt.cameraErrorMessage'));
- })
- .finally(() => {
- isCapturing.current = false;
- });
- }, [cameraPermissionStatus, flash, hasFlash, onCapture, translate, askForPermissions]);
-
- return (
-
-
-
- {/* Camera viewfinder area */}
-
- {cameraPermissionStatus !== RESULTS.GRANTED && (
-
-
- {translate('receipt.takePhoto')}
- {translate('receipt.cameraAccess')}
-
-
- )}
- {cameraPermissionStatus === RESULTS.GRANTED && device == null && (
-
-
-
- )}
- {cameraPermissionStatus === RESULTS.GRANTED && device != null && (
-
-
-
-
-
- )}
-
-
- {/* Bottom controls */}
-
- {/* Flash toggle */}
- setFlash((prev) => !prev)}
- sentryLabel="AttachmentCamera-FlashToggle"
- >
-
-
-
- {/* Shutter button */}
-
-
-
-
- {/* Camera flip button */}
- setCameraPosition((prev) => (prev === 'back' ? 'front' : 'back'))}
- sentryLabel="AttachmentCamera-FlipCamera"
- >
-
-
-
-
-
- );
-}
-
-AttachmentCamera.displayName = 'AttachmentCamera';
-
-export default AttachmentCamera;
-export type {CapturedPhoto};
diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx
index 7463e2b4add4..076b44e5feb8 100644
--- a/src/components/AttachmentPicker/index.native.tsx
+++ b/src/components/AttachmentPicker/index.native.tsx
@@ -24,8 +24,7 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type {FileObject, ImagePickerResponse as FileResponse} from '@src/types/utils/Attachment';
import type IconAsset from '@src/types/utils/IconAsset';
-import AttachmentCamera from './AttachmentCamera';
-import type {CapturedPhoto} from './AttachmentCamera';
+import launchCamera from './launchCamera/launchCamera';
import type AttachmentPickerProps from './types';
type LocalCopy = {
@@ -137,10 +136,6 @@ function AttachmentPicker({
const onClosed = useRef<() => void>(() => {});
const popoverRef = useRef(null);
- // In-app camera state — uses VisionCamera to keep the app in the foreground during photo capture
- const [showAttachmentCamera, setShowAttachmentCamera] = useState(false);
- const cameraResolveRef = useRef<((photos?: CapturedPhoto[]) => void) | null>(null);
-
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
@@ -154,43 +149,6 @@ function AttachmentPicker({
[translate],
);
- /**
- * Launch the in-app camera using VisionCamera.
- * Returns a Promise that resolves with the captured photo as an Asset-compatible object,
- * or resolves with void if the user closes the camera without capturing.
- */
- const launchInAppCamera = (): Promise => {
- return new Promise((resolve) => {
- cameraResolveRef.current = (photos?: CapturedPhoto[]) => {
- if (!photos || photos.length === 0) {
- resolve();
- return;
- }
- const assets: Asset[] = photos.map((photo) => ({
- uri: photo.uri,
- fileName: photo.fileName,
- type: photo.type,
- width: photo.width,
- height: photo.height,
- }));
- resolve(assets);
- };
- setShowAttachmentCamera(true);
- });
- };
-
- const handleCameraCapture = (photos: CapturedPhoto[]) => {
- setShowAttachmentCamera(false);
- cameraResolveRef.current?.(photos);
- cameraResolveRef.current = null;
- };
-
- const handleCameraClose = () => {
- setShowAttachmentCamera(false);
- cameraResolveRef.current?.();
- cameraResolveRef.current = null;
- };
-
/**
* Common image picker handling
*
@@ -343,12 +301,12 @@ function AttachmentPicker({
data.unshift({
icon: icons.Camera,
textTranslationKey: 'attachmentPicker.takePhoto',
- pickAttachment: launchInAppCamera,
+ pickAttachment: () => showImagePicker(launchCamera),
});
}
return data;
- }, [icons.Camera, icons.Paperclip, icons.Gallery, showDocumentPicker, shouldHideGalleryOption, shouldHideCameraOption, showImagePicker, launchInAppCamera]);
+ }, [icons.Camera, icons.Paperclip, icons.Gallery, showDocumentPicker, shouldHideGalleryOption, shouldHideCameraOption, showImagePicker]);
const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: -1, maxIndex: menuItemData.length - 1, isActive: isVisible});
@@ -570,13 +528,6 @@ function AttachmentPicker({
))}
- {showAttachmentCamera && (
-
- )}
{renderChildren()}
>
);
diff --git a/src/components/Icon/chunks/expensify-icons.chunk.ts b/src/components/Icon/chunks/expensify-icons.chunk.ts
index 041be5dd02f4..d4274e9e7f5e 100644
--- a/src/components/Icon/chunks/expensify-icons.chunk.ts
+++ b/src/components/Icon/chunks/expensify-icons.chunk.ts
@@ -35,7 +35,6 @@ import Building from '@assets/images/building.svg';
import Buildings from '@assets/images/buildings.svg';
import CalendarSolid from '@assets/images/calendar-solid.svg';
import Calendar from '@assets/images/calendar.svg';
-import CameraFlip from '@assets/images/camera-flip.svg';
import Camera from '@assets/images/camera.svg';
import CarCircleSlash from '@assets/images/car-circle-slash.svg';
import CarPlus from '@assets/images/car-plus.svg';
@@ -289,7 +288,6 @@ const Expensicons = {
Buildings,
Calendar,
Camera,
- CameraFlip,
Car,
CarPlus,
Cash,
diff --git a/src/languages/de.ts b/src/languages/de.ts
index f26613f58449..f75f4710bafa 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1165,7 +1165,6 @@ const translations: TranslationDeepObject = {
flash: 'Blitz',
multiScan: 'Mehrfachscan',
shutter: 'Verschluss',
- flipCamera: 'Kamera wechseln',
gallery: 'Galerie',
deleteReceipt: 'Beleg löschen',
deleteConfirmation: 'Sind Sie sicher, dass Sie diesen Beleg löschen möchten?',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index a88b7dc2cd64..b421680bceb4 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1221,7 +1221,6 @@ const translations = {
flash: 'flash',
multiScan: 'multi-scan',
shutter: 'shutter',
- flipCamera: 'flip camera',
gallery: 'gallery',
deleteReceipt: 'Delete receipt',
deleteConfirmation: 'Are you sure you want to delete this receipt?',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 94ea3d6623bb..754faf572c2e 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1086,7 +1086,6 @@ const translations: TranslationDeepObject = {
flash: 'flash',
multiScan: 'escaneo múltiple',
shutter: 'obturador',
- flipCamera: 'cambiar cámara',
gallery: 'galería',
deleteReceipt: 'Eliminar recibo',
deleteConfirmation: '¿Estás seguro de que quieres borrar este recibo?',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 119438e3629f..a3769042dec7 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1169,7 +1169,6 @@ const translations: TranslationDeepObject = {
flash: 'flash',
multiScan: 'numérisation multiple',
shutter: 'obturateur',
- flipCamera: 'changer de caméra',
gallery: 'galerie',
deleteReceipt: 'Supprimer le reçu',
deleteConfirmation: 'Voulez-vous vraiment supprimer ce reçu ?',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index c9c8209d4063..96e7a9aaa3f2 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1167,7 +1167,6 @@ const translations: TranslationDeepObject = {
flash: 'flash',
multiScan: 'scansione multipla',
shutter: 'otturatore',
- flipCamera: 'cambia fotocamera',
gallery: 'galleria',
deleteReceipt: 'Elimina ricevuta',
deleteConfirmation: 'Sei sicuro di voler eliminare questa ricevuta?',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 407a92f60fec..6f4eaf7db794 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1150,7 +1150,6 @@ const translations: TranslationDeepObject = {
flash: 'フラッシュ',
multiScan: 'マルチスキャン',
shutter: 'シャッター',
- flipCamera: 'カメラ切替',
gallery: 'ギャラリー',
deleteReceipt: '領収書を削除',
deleteConfirmation: 'この領収書を削除してもよろしいですか?',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index dbe5744bc7fb..16c0adf0ad67 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1166,7 +1166,6 @@ const translations: TranslationDeepObject = {
flash: 'flits',
multiScan: 'meerscannen',
shutter: 'sluiter',
- flipCamera: 'camera wisselen',
gallery: 'galerij',
deleteReceipt: 'Bon verwijderen',
deleteConfirmation: 'Weet je zeker dat je deze bon wilt verwijderen?',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index d61887f0ed3e..2b7c3f9a9ee4 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1165,7 +1165,6 @@ const translations: TranslationDeepObject = {
flash: 'błysk',
multiScan: 'wielokrotne skanowanie',
shutter: 'migawka',
- flipCamera: 'przełącz kamerę',
gallery: 'galeria',
deleteReceipt: 'Usuń paragon',
deleteConfirmation: 'Czy na pewno chcesz usunąć ten paragon?',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index b335a965bae3..d02861017ba7 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1165,7 +1165,6 @@ const translations: TranslationDeepObject = {
flash: 'flash',
multiScan: 'escaneamento múltiplo',
shutter: 'obturador',
- flipCamera: 'trocar câmera',
gallery: 'galeria',
deleteReceipt: 'Excluir recibo',
deleteConfirmation: 'Tem certeza de que deseja excluir este recibo?',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 91512e9023c0..95b8fbe69c7a 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1126,7 +1126,6 @@ const translations: TranslationDeepObject = {
flash: '闪光',
multiScan: '多重扫描',
shutter: '快门',
- flipCamera: '切换摄像头',
gallery: '图库',
deleteReceipt: '删除收据',
deleteConfirmation: '确定要删除这张收据吗?',