Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32162153
D14987.1765044684.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
16 KB
Referenced Files
None
Subscribers
None
D14987.1765044684.diff
View Options
diff --git a/native/media/camera-modal.react.js b/native/media/camera-modal.react.js
--- a/native/media/camera-modal.react.js
+++ b/native/media/camera-modal.react.js
@@ -13,7 +13,6 @@
Animated,
Easing,
} from 'react-native';
-import { RNCamera } from 'react-native-camera';
import filesystem from 'react-native-fs';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Orientation from 'react-native-orientation-locker';
@@ -30,11 +29,18 @@
runOnJS,
interpolate,
Extrapolate,
+ useAnimatedProps,
} from 'react-native-reanimated';
import {
SafeAreaView,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
+import {
+ Camera,
+ useCameraPermission,
+ useCameraDevice,
+ type CameraProps,
+} from 'react-native-vision-camera';
import { pathFromURI, filenameFromPathOrURI } from 'lib/media/file-utils.js';
import { useIsAppForegrounded } from 'lib/shared/lifecycle-utils.js';
@@ -53,19 +59,12 @@
import type { NativeMethods } from '../types/react-native.js';
import { clamp } from '../utils/animation-utils.js';
+Reanimated.addWhitelistedNativeProps({
+ zoom: true,
+});
+const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera);
+
const maxZoom = 16;
-const zoomUpdateFactor = (() => {
- if (Platform.OS === 'ios') {
- return 0.002;
- }
- if (Platform.OS === 'android' && Platform.Version > 26) {
- return 0.005;
- }
- if (Platform.OS === 'android' && Platform.Version > 23) {
- return 0.01;
- }
- return 0.03;
-})();
const stagingModeAnimationConfig = {
duration: 150,
@@ -96,19 +95,11 @@
} catch (e) {}
}
-type RNCameraStatus = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED';
-
type TouchableOpacityInstance = React.AbstractComponent<
React.ElementConfig<typeof TouchableOpacity>,
NativeMethods,
>;
-type AutoFocusPointOfInterest = ?{
- +x: number,
- +y: number,
- +autoExposure?: boolean,
-};
-
type Dimensions = {
+x: number,
+y: number,
@@ -276,7 +267,6 @@
const CameraModal: React.ComponentType<Props> = React.memo<Props>(
function CameraModal(props: Props) {
- const dimensions = useSelector(state => state.dimensions);
const deviceCameraInfo = useSelector(state => state.deviceCameraInfo);
const deviceOrientation = useSelector(state => state.deviceOrientation);
const foreground = useIsAppForegrounded();
@@ -301,18 +291,16 @@
};
}, []);
- const [flashMode, setFlashMode] = React.useState(
- RNCamera.Constants.FlashMode.off,
- );
+ const [flashMode, setFlashMode] = React.useState('off');
const changeFlashMode = React.useCallback(() => {
setFlashMode(prevFlashMode => {
- if (prevFlashMode === RNCamera.Constants.FlashMode.on) {
- return RNCamera.Constants.FlashMode.off;
- } else if (prevFlashMode === RNCamera.Constants.FlashMode.off) {
- return RNCamera.Constants.FlashMode.auto;
+ if (prevFlashMode === 'on') {
+ return 'off';
+ } else if (prevFlashMode === 'off') {
+ return 'auto';
}
- return RNCamera.Constants.FlashMode.on;
+ return 'on';
});
}, []);
@@ -330,135 +318,89 @@
const cameraIDsFetched = React.useRef(false);
- const fetchCameraIDs = React.useCallback(
- async (camera: RNCamera) => {
- if (cameraIDsFetched.current) {
- return;
- }
- cameraIDsFetched.current = true;
-
- const deviceCameras = await camera.getCameraIdsAsync();
-
- let hasFront = false,
- hasBack = false,
- i = 0;
- while ((!hasFront || !hasBack) && i < deviceCameras.length) {
- const deviceCamera = deviceCameras[i];
- if (deviceCamera.type === RNCamera.Constants.Type.front) {
- hasFront = true;
- } else if (deviceCamera.type === RNCamera.Constants.Type.back) {
- hasBack = true;
- }
- i++;
+ const fetchCameraIDs = React.useCallback(async () => {
+ if (cameraIDsFetched.current) {
+ return;
+ }
+ cameraIDsFetched.current = true;
+
+ const deviceCameras = Camera.getAvailableCameraDevices();
+
+ let hasFront = false,
+ hasBack = false,
+ i = 0;
+ while ((!hasFront || !hasBack) && i < deviceCameras.length) {
+ const deviceCamera = deviceCameras[i];
+ if (deviceCamera.position === 'front') {
+ hasFront = true;
+ } else if (deviceCamera.position === 'back') {
+ hasBack = true;
}
+ i++;
+ }
- const nextHasCamerasOnBothSides = hasFront && hasBack;
- const defaultUseFrontCamera = !hasBack && hasFront;
- if (nextHasCamerasOnBothSides !== hasCamerasOnBothSides) {
- setHasCamerasOnBothSides(nextHasCamerasOnBothSides);
- }
- const {
- hasCamerasOnBothSides: oldHasCamerasOnBothSides,
- defaultUseFrontCamera: oldDefaultUseFrontCamera,
- } = deviceCameraInfo;
- if (
- nextHasCamerasOnBothSides !== oldHasCamerasOnBothSides ||
- defaultUseFrontCamera !== oldDefaultUseFrontCamera
- ) {
- dispatch({
- type: updateDeviceCameraInfoActionType,
- payload: {
- hasCamerasOnBothSides: nextHasCamerasOnBothSides,
- defaultUseFrontCamera,
- },
- });
- }
- },
- [deviceCameraInfo, dispatch, hasCamerasOnBothSides],
- );
+ const nextHasCamerasOnBothSides = hasFront && hasBack;
+ const defaultUseFrontCamera = !hasBack && hasFront;
+ if (nextHasCamerasOnBothSides !== hasCamerasOnBothSides) {
+ setHasCamerasOnBothSides(nextHasCamerasOnBothSides);
+ }
+ const {
+ hasCamerasOnBothSides: oldHasCamerasOnBothSides,
+ defaultUseFrontCamera: oldDefaultUseFrontCamera,
+ } = deviceCameraInfo;
+ if (
+ nextHasCamerasOnBothSides !== oldHasCamerasOnBothSides ||
+ defaultUseFrontCamera !== oldDefaultUseFrontCamera
+ ) {
+ dispatch({
+ type: updateDeviceCameraInfoActionType,
+ payload: {
+ hasCamerasOnBothSides: nextHasCamerasOnBothSides,
+ defaultUseFrontCamera,
+ },
+ });
+ }
+ }, [deviceCameraInfo, dispatch, hasCamerasOnBothSides]);
- const [autoFocusPointOfInterest, setAutoFocusPointOfInterest] =
- React.useState<AutoFocusPointOfInterest>();
+ const cameraRef = React.useRef<?Camera>();
const focusOnPoint = React.useCallback(
([inputX, inputY]: [number, number]) => {
- const { width: screenWidth, height: screenHeight } = dimensions;
- const relativeX = inputX / screenWidth;
- const relativeY = inputY / screenHeight;
-
- // react-native-camera's autoFocusPointOfInterest prop is based on a
- // LANDSCAPE-LEFT orientation, so we need to map to that
- let x, y;
- if (deviceOrientation === 'LANDSCAPE-LEFT') {
- x = relativeX;
- y = relativeY;
- } else if (deviceOrientation === 'LANDSCAPE-RIGHT') {
- x = 1 - relativeX;
- y = 1 - relativeY;
- } else if (deviceOrientation === 'PORTRAIT-UPSIDEDOWN') {
- x = 1 - relativeY;
- y = relativeX;
- } else {
- x = relativeY;
- y = 1 - relativeX;
- }
- setAutoFocusPointOfInterest(
- Platform.OS === 'ios' ? { x, y, autoExposure: true } : { x, y },
- );
+ const camera = cameraRef.current;
+ invariant(camera, 'camera ref should be set');
+ void camera.focus({ x: inputX, y: inputY });
},
- [deviceOrientation, dimensions],
+ [],
);
- const [zoom, setZoom] = React.useState(0);
-
- const updateZoom = React.useCallback((nextZoom: number) => {
- setZoom(nextZoom);
- }, []);
-
const [stagingMode, setStagingMode] = React.useState(false);
const [pendingPhotoCapture, setPendingPhotoCapture] =
React.useState<?PhotoCapture>();
- const cameraRef = React.useRef<?RNCamera>();
-
const takePhoto = React.useCallback(async () => {
const camera = cameraRef.current;
invariant(camera, 'camera ref should be set');
setStagingMode(true);
- // We avoid flipping useFrontCamera if we discover we don't
- // actually have a back camera since it causes a bit of lag, but this
- // means there are cases where it is false but we are actually using the
- // front camera
- const {
- hasCamerasOnBothSides: hasCamerasOnBothSidesFromDeviceInfo,
- defaultUseFrontCamera,
- } = deviceCameraInfo;
- const usingFrontCamera =
- useFrontCamera ||
- (!hasCamerasOnBothSidesFromDeviceInfo && defaultUseFrontCamera);
-
const startTime = Date.now();
- const photoPromise = camera.takePictureAsync({
- pauseAfterCapture: Platform.OS === 'android',
- mirrorImage: usingFrontCamera,
- fixOrientation: true,
+ const photoPromise = camera.takePhoto({
+ flash: flashMode,
});
- if (Platform.OS === 'ios') {
- camera.pausePreview();
- }
- const { uri, width, height } = await photoPromise;
+ const { path: uri, width, height } = await photoPromise;
+
const filename = filenameFromPathOrURI(uri);
invariant(
filename,
- `unable to parse filename out of react-native-camera URI ${uri}`,
+ `unable to parse filename out of react-native-vision-camera URI ${uri}`,
);
const now = Date.now();
const nextPendingPhotoCapture = {
step: 'photo_capture',
- uri,
+ // If you want to consume this file (e.g. for displaying it in an <Image> component), you might have to add the file:// prefix.
+ // https://react-native-vision-camera.com/docs/api/interfaces/PhotoFile#path
+ uri: uri.startsWith('file://') ? uri : 'file://' + uri,
dimensions: { width, height },
filename,
time: now - startTime,
@@ -467,11 +409,8 @@
sendTime: 0,
retries: 0,
};
-
- setAutoFocusPointOfInterest(undefined);
- setZoom(0);
setPendingPhotoCapture(nextPendingPhotoCapture);
- }, [deviceCameraInfo, useFrontCamera]);
+ }, [flashMode]);
const close = React.useCallback(() => {
if (overlayContext && navigation.goBackOnce) {
@@ -499,7 +438,6 @@
const clearPendingImage = React.useCallback(() => {
invariant(cameraRef.current, 'camera ref should be set');
- cameraRef.current.resumePreview();
setStagingMode(false);
setPendingPhotoCapture();
}, []);
@@ -702,42 +640,25 @@
);
const zoomBase = useSharedValue(1);
- const zoomReported = useSharedValue(1);
const currentZoom = useSharedValue(1);
+ const animatedProps = useAnimatedProps<CameraProps>(
+ () => ({ zoom: currentZoom.value }),
+ [currentZoom],
+ );
+
const onPinchUpdate = React.useCallback(
(pinchScale: number) => {
'worklet';
currentZoom.value = clamp(zoomBase.value * pinchScale, 1, 8);
- if (
- Math.abs(currentZoom.value / zoomReported.value - 1) >
- zoomUpdateFactor
- ) {
- zoomReported.value = currentZoom.value;
- const cameraZoomFactor = interpolate(
- zoomReported.value,
- [1, 8],
- [0, 1],
- Extrapolate.CLAMP,
- );
- runOnJS(updateZoom)(cameraZoomFactor);
- }
},
- [currentZoom, updateZoom, zoomBase.value, zoomReported],
+ [currentZoom, zoomBase.value],
);
const onPinchEnd = React.useCallback(() => {
'worklet';
- zoomReported.value = currentZoom.value;
zoomBase.value = currentZoom.value;
- const cameraZoomFactor = interpolate(
- zoomReported.value,
- [1, 8],
- [0, 1],
- Extrapolate.CLAMP,
- );
- runOnJS(updateZoom)(cameraZoomFactor);
- }, [currentZoom, updateZoom, zoomBase, zoomReported]);
+ }, [currentZoom, zoomBase]);
const gesture = React.useMemo(() => {
const pinchGesture = Gesture.Pinch()
@@ -793,18 +714,11 @@
const prevDeviceOrientation = React.useRef<?Orientations>();
React.useEffect(() => {
if (deviceOrientation !== prevDeviceOrientation.current) {
- setAutoFocusPointOfInterest(null);
cancelFocusAnimation();
}
prevDeviceOrientation.current = deviceOrientation;
}, [cancelFocusAnimation, deviceOrientation]);
- React.useEffect(() => {
- if (foreground && cameraRef.current) {
- void cameraRef.current.refreshAuthorizationStatus();
- }
- }, [foreground]);
-
const prevStagingMode = React.useRef(false);
React.useEffect(() => {
if (stagingMode && !prevStagingMode.current) {
@@ -844,16 +758,17 @@
return [styles.container, containerAnimatedStyle];
}, [containerAnimatedStyle, overlayContext]);
- const renderCamera = ({
- camera,
- status,
- }: {
- +camera: RNCamera & { +_cameraHandle?: mixed, ... },
- status: RNCameraStatus,
- ...
- }): React.Node => {
- if (camera && camera._cameraHandle) {
- void fetchCameraIDs(camera);
+ const { hasPermission, requestPermission } = useCameraPermission();
+
+ React.useEffect(() => {
+ if (foreground && !hasPermission) {
+ void requestPermission();
+ }
+ }, [foreground, hasPermission, requestPermission]);
+
+ const renderCamera = (): React.Node => {
+ if (cameraRef.current) {
+ void fetchCameraIDs();
}
if (stagingMode) {
return renderStagingView();
@@ -862,7 +777,7 @@
return (
<SafeAreaView style={styles.fill}>
<View style={styles.fill}>
- {renderCameraContent(status)}
+ {renderCameraContent()}
<TouchableOpacity
onPress={close}
onLayout={onCloseButtonLayout}
@@ -908,10 +823,8 @@
);
};
- const renderCameraContent = (status: RNCameraStatus): React.Node => {
- if (status === 'PENDING_AUTHORIZATION') {
- return <ContentLoading fillType="flex" colors={colors.dark} />;
- } else if (status === 'NOT_AUTHORIZED') {
+ const renderCameraContent = (): React.Node => {
+ if (!hasPermission) {
return (
<View style={styles.authorizationDeniedContainer}>
<Text style={styles.authorizationDeniedText}>
@@ -936,9 +849,9 @@
}
let flashIcon;
- if (flashMode === RNCamera.Constants.FlashMode.on) {
+ if (flashMode === 'on') {
flashIcon = <Icon name="flash" style={styles.flashIcon} />;
- } else if (flashMode === RNCamera.Constants.FlashMode.off) {
+ } else if (flashMode === 'off') {
flashIcon = <Icon name="flash-off" style={styles.flashIcon} />;
} else {
flashIcon = (
@@ -978,25 +891,35 @@
};
const statusBar = isActive ? <ConnectedStatusBar hidden /> : null;
- const type = useFrontCamera
- ? RNCamera.Constants.Type.front
- : RNCamera.Constants.Type.back;
+ const device = useCameraDevice(useFrontCamera ? 'front' : 'back');
+ if (!device) {
+ return (
+ <View style={styles.authorizationDeniedContainer}>
+ <Text style={styles.authorizationDeniedText}>
+ No camera is available on your device
+ </Text>
+ </View>
+ );
+ }
+
+ const camera = hasPermission ? (
+ <ReanimatedCamera
+ style={StyleSheet.absoluteFill}
+ ref={cameraRef}
+ device={device}
+ isActive
+ animatedProps={animatedProps}
+ photo
+ torch={flashMode === 'auto' ? 'off' : flashMode}
+ maxZoom={maxZoom}
+ />
+ ) : null;
+
return (
<Reanimated.View style={containerStyle}>
{statusBar}
- <RNCamera
- type={type}
- captureAudio={false}
- maxZoom={maxZoom}
- zoom={zoom}
- flashMode={flashMode}
- autoFocusPointOfInterest={autoFocusPointOfInterest}
- style={styles.fill}
- androidCameraPermissionOptions={null}
- ref={cameraRef}
- >
- {renderCamera}
- </RNCamera>
+ {camera}
+ <View style={StyleSheet.absoluteFill}>{renderCamera()}</View>
<Reanimated.View style={overlayStyle} pointerEvents="none" />
</Reanimated.View>
);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 6, 6:11 PM (19 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5840032
Default Alt Text
D14987.1765044684.diff (16 KB)
Attached To
Mode
D14987: [native][RN79][skip-ci] Migrate CameraModal to react-native-vision-camera
Attached
Detach File
Event Timeline
Log In to Comment