fix: use in-app VisionCamera for chat attachment photos#87681
fix: use in-app VisionCamera for chat attachment photos#87681
Conversation
Replace the external system camera intent with an in-app VisionCamera modal for the "Take photo" option in the attachment picker on both Android and iOS. This keeps Expensify in the foreground during photo capture, preventing the OS from reclaiming the app's memory. Addresses deploy blockers from previous implementation: - Tap-to-focus with animated focus indicator - ScrollView for permission-denied view in landscape mode - Safe area insets for camera hole/nav bar/notch handling - AppState listener to refresh camera after granting permissions - Camera flip (front/back) support - Proper modal with supportedOrientations for landscape Co-authored-by: Shridhar Goel <ShridharGoel@users.noreply.github.com>
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index 308b3731..677cd78c 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1181,7 +1181,7 @@ const translations: TranslationDeepObject<typeof en> = {
dropTitle: '[de] Let it go',
dropMessage: '[de] Drop your file here',
flash: '[de] flash',
- flipCamera: '[de] Flip camera',
+ flipCamera: 'Kamera drehen',
multiScan: '[de] multi-scan',
shutter: '[de] shutter',
gallery: '[de] gallery',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index ca408514..f92e48d2 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1181,7 +1181,7 @@ const translations: TranslationDeepObject<typeof en> = {
dropTitle: '[fr] Let it go',
dropMessage: '[fr] Drop your file here',
flash: '[fr] flash',
- flipCamera: '[fr] Flip camera',
+ flipCamera: 'Inverser la caméra',
multiScan: '[fr] multi-scan',
shutter: '[fr] shutter',
gallery: '[fr] gallery',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 6dd08737..74d5e357 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1181,7 +1181,7 @@ const translations: TranslationDeepObject<typeof en> = {
dropTitle: '[it] Let it go',
dropMessage: '[it] Drop your file here',
flash: '[it] flash',
- flipCamera: '[it] Flip camera',
+ flipCamera: 'Inverti fotocamera',
multiScan: '[it] multi-scan',
shutter: '[it] shutter',
gallery: '[it] gallery',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 01d09641..12acef6e 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1181,7 +1181,7 @@ const translations: TranslationDeepObject<typeof en> = {
dropTitle: '[ja] Let it go',
dropMessage: '[ja] Drop your file here',
flash: '[ja] flash',
- flipCamera: '[ja] Flip camera',
+ flipCamera: 'カメラを反転',
multiScan: '[ja] multi-scan',
shutter: '[ja] shutter',
gallery: '[ja] gallery',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 6ba55c2f..c44fda45 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1181,7 +1181,7 @@ const translations: TranslationDeepObject<typeof en> = {
dropTitle: '[nl] Let it go',
dropMessage: '[nl] Drop your file here',
flash: '[nl] flash',
- flipCamera: '[nl] Flip camera',
+ flipCamera: 'Camera omdraaien',
multiScan: '[nl] multi-scan',
shutter: '[nl] shutter',
gallery: '[nl] gallery',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 7c85693c..87fca8c6 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1181,7 +1181,7 @@ const translations: TranslationDeepObject<typeof en> = {
dropTitle: '[pl] Let it go',
dropMessage: '[pl] Drop your file here',
flash: '[pl] flash',
- flipCamera: '[pl] Flip camera',
+ flipCamera: 'Odwróć kamerę',
multiScan: '[pl] multi-scan',
shutter: '[pl] shutter',
gallery: '[pl] gallery',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 778625c3..914dce1b 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1183,7 +1183,7 @@ const translations: TranslationDeepObject<typeof en> = {
dropTitle: '[pt-BR] Let it go',
dropMessage: '[pt-BR] Drop your file here',
flash: '[pt-BR] flash',
- flipCamera: '[pt-BR] Flip camera',
+ flipCamera: 'Inverter câmera',
multiScan: '[pt-BR] multi-scan',
shutter: '[pt-BR] shutter',
gallery: '[pt-BR] gallery',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 0f4ab76c..0263b4d6 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1194,7 +1194,7 @@ const translations: TranslationDeepObject<typeof en> = {
dropTitle: '[zh-hans] Let it go',
dropMessage: '[zh-hans] Drop your file here',
flash: '[zh-hans] flash',
- flipCamera: '[zh-hans] Flip camera',
+ flipCamera: '翻转摄像头',
multiScan: '[zh-hans] multi-scan',
shutter: '[zh-hans] shutter',
gallery: '[zh-hans] gallery',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
Co-authored-by: Shridhar Goel <ShridharGoel@users.noreply.github.com>
|
Fixed the failing |
Co-authored-by: Shridhar Goel <ShridharGoel@users.noreply.github.com>
|
Fixed the failing ESLint check: added missing |
The AttachmentCamera component imports react-native-vision-camera which tries to initialize the native Camera Module at import time. In the Jest environment there is no native module, causing all test suites that transitively import AttachmentPicker to fail with "system/camera-module-not-found". Co-authored-by: Shridhar Goel <ShridharGoel@users.noreply.github.com>
|
Fixed the failing checks: added a Jest mock for The |
|
⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai. |
Co-authored-by: Shridhar Goel <ShridharGoel@users.noreply.github.com>
|
Fixed the failing Prettier check: ran |
|
MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR. Next steps: A human should review the recent commits and resolve the conflicting CI errors manually. |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
Explanation of Change
Replaces the external system camera intent with an in-app VisionCamera modal when taking photos via the chat attachment picker ("Take photo") on both Android and iOS.
Why: On Android, launching the external camera activity backgrounds Expensify, allowing the OS to reclaim memory and crash the app (#84018). The previous fix (PR #86981) addressed this but was reverted due to multiple deploy blockers. This PR re-implements the feature while addressing all reported issues.
Key improvements over the reverted PR:
Gesture.Tap()handler with animated focus indicator (same pattern asIOURequestStepScan)ScrollViewwith landscape-specific paddingsupportedOrientations={['portrait', 'landscape']}and lets VisionCamera handle orientation nativelyonCapturecallback returns photo data directly, modal closes before resolvinguseSafeAreaInsets) on all four edgesAppStatelistener refreshes permission status when app returns to foregroundChanges:
AttachmentCameracomponent usingreact-native-vision-camera(same library used by receipt scan)AttachmentPicker/index.native.tsxto show in-app camera instead of launching external camera intentcamera-flip.svgicon andCameraFlipicon registrationreceipt.flipCameratranslation key to all language filesFixed Issues
$ #84018
Tests
Offline tests
The camera feature is device-local and does not require network connectivity. Taking a photo offline should work normally — the attachment will be queued for upload when connectivity returns.
QA Steps
Same as tests above, performed on:
Android native device
iOS native device
Verify that no errors appear in the JS console
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Checks run
Screenshots/Videos
Android: Native
Android: mWeb Chrome
N/A — camera feature is native-only
iOS: Native
iOS: mWeb Safari
N/A — camera feature is native-only
MacOS: Chrome / Safari
N/A — camera feature is native-only, web attachment picker is unchanged
Screenshots/Videosundefined