Enable multi-camera (#1461)

* Use multi-cam; closes #1458

* Typescript conversion
This commit is contained in:
Amanda Bullington
2024-04-23 14:26:57 -07:00
committed by GitHub
parent f10b25fecc
commit 2b1bcd9363
15 changed files with 70 additions and 65 deletions

View File

@@ -1472,12 +1472,12 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: d3f49c53809116a5d38da093a8aa78bf551aed09
BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
FasterImage: 60d0750ddbcefff0070c4c17309c2d1d6cc650f0
FBLazyVector: 9f533d5a4c75ca77c8ed774aced1a91a0701781e
FBReactNativeSpec: 40b791f4a1df779e7e4aa12c000319f4f216d40a
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
hermes-engine: 39589e9c297d024e90fe68f6830ff86c4e01498a
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
MMKV: ed58ad794b3f88c24d604a5b74f3fba17fcbaf74
@@ -1568,4 +1568,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: eebd76aa39f99b44754431ed68ce0cfbfc5ec2f7
COCOAPODS: 1.14.2
COCOAPODS: 1.14.3

View File

@@ -941,11 +941,7 @@
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-Wl",
"-ld_classic",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
@@ -1017,11 +1013,7 @@
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-Wl",
"-ld_classic",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;

10
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"@react-native-clipboard/clipboard": "^1.13.2",
"@react-native-community/datetimepicker": "^7.6.3",
"@react-native-community/geolocation": "^3.2.1",
"@react-native-community/hooks": "^3.0.0",
"@react-native-community/netinfo": "^11.3.1",
"@react-native-community/slider": "^4.5.0",
"@react-native-picker/picker": "^2.7.2",
@@ -4811,6 +4812,15 @@
"react-native": "*"
}
},
"node_modules/@react-native-community/hooks": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-native-community/hooks/-/hooks-3.0.0.tgz",
"integrity": "sha512-g2OyxXHfwIytXUJitBR6Z/ISoOfp0WKx5FOv+NqJ/CrWjRDcTw6zXE5I1C9axfuh30kJqzWchVfCDrkzZYTxqg==",
"peerDependencies": {
"react": ">=17.0.2",
"react-native": ">=0.65"
}
},
"node_modules/@react-native-community/netinfo": {
"version": "11.3.1",
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.1.tgz",

View File

@@ -46,6 +46,7 @@
"@react-native-clipboard/clipboard": "^1.13.2",
"@react-native-community/datetimepicker": "^7.6.3",
"@react-native-community/geolocation": "^3.2.1",
"@react-native-community/hooks": "^3.0.0",
"@react-native-community/netinfo": "^11.3.1",
"@react-native-community/slider": "^4.5.0",
"@react-native-picker/picker": "^2.7.2",

View File

@@ -4,8 +4,8 @@ import { useNavigation } from "@react-navigation/native";
import classnames from "classnames";
import FadeInOutView from "components/Camera/FadeInOutView";
import useRotation from "components/Camera/hooks/useRotation";
import useTakePhoto from "components/Camera/hooks/useTakePhoto";
import useZoom from "components/Camera/hooks/useZoom";
import useTakePhoto from "components/Camera/hooks/useTakePhoto.ts";
import useZoom from "components/Camera/hooks/useZoom.ts";
import { Body1, INatIcon, TaxonResult } from "components/SharedComponents";
import { View } from "components/styledComponents";
import type { Node } from "react";

View File

@@ -1,6 +1,6 @@
// @flow
import { useNavigation } from "@react-navigation/native";
import CameraView from "components/Camera/CameraView";
import CameraView from "components/Camera/CameraView.tsx";
import type { Node } from "react";
import React, {
useEffect,

View File

@@ -1,5 +1,3 @@
// @flow
import { useRoute } from "@react-navigation/native";
import type { Node } from "react";
import React, {
@@ -15,12 +13,15 @@ const CameraContainer = ( ): Node => {
const { params } = useRoute( );
const addEvidence = params?.addEvidence;
const cameraType = params?.camera;
const [cameraPosition, setCameraPosition] = useState( "back" );
const device = useCameraDevice( cameraPosition );
if ( !device ) {
return null;
}
const [cameraPosition, setCameraPosition] = useState<"front" | "back">( "back" );
// https://react-native-vision-camera.com/docs/guides/devices#selecting-multi-cams
const device = useCameraDevice( cameraPosition, {
physicalDevices: [
"ultra-wide-angle-camera",
"wide-angle-camera",
"telephoto-camera"
]
} );
return (
<CameraWithDevice

View File

@@ -1,3 +1,4 @@
import { useAppState } from "@react-native-community/hooks";
import { useIsFocused } from "@react-navigation/native";
import VeryBadIpadRotator from "components/SharedComponents/VeryBadIpadRotator";
import { View } from "components/styledComponents";
@@ -8,6 +9,7 @@ import {
Gesture, GestureDetector
} from "react-native-gesture-handler";
import Reanimated from "react-native-reanimated";
import type { CameraProps, CameraRuntimeError } from "react-native-vision-camera";
import { Camera, useCameraFormat } from "react-native-vision-camera";
import { orientationPatch } from "sharedHelpers/visionCameraPatches";
import useDeviceOrientation from "sharedHooks/useDeviceOrientation";
@@ -19,33 +21,33 @@ Reanimated.addWhitelistedNativeProps( {
zoom: true
} );
type Props = {
interface Props {
animatedProps: CameraProps,
cameraRef: Object,
device: Object,
frameProcessor?: Function,
onCameraError?: Function,
onCaptureError?: Function,
onClassifierError?: Function,
onDeviceNotSupported?: Function,
onCaptureError?: Function,
onCameraError?: Function,
frameProcessor?: Function,
animatedProps: unknown,
onZoomStart?: Function,
onZoomChange?: Function,
onZoomStart?: Function,
resizeMode?: string
};
}
// A container for the Camera component
// that has logic that applies to both use cases in StandardCamera and AICamera
const CameraView = ( {
animatedProps,
cameraRef,
device,
frameProcessor,
onCameraError,
onCaptureError,
onClassifierError,
onDeviceNotSupported,
onCaptureError,
onCameraError,
frameProcessor,
animatedProps,
onZoomStart,
onZoomChange,
onZoomStart,
resizeMode
}: Props ): Node => {
const [focusAvailable, setFocusAvailable] = useState( true );
@@ -54,6 +56,8 @@ const CameraView = ( {
// check if camera page is active
const isFocused = useIsFocused( );
const appState = useAppState( );
const isActive = isFocused && appState === "active";
// Select a format that provides the highest resolution for photos and videos
const iosFormat = useCameraFormat( device, [
@@ -98,8 +102,7 @@ const CameraView = ( {
.onStart( e => singleTapToFocus( e ) );
const onError = useCallback(
error => {
// error is a CameraRuntimeError =
( error: CameraRuntimeError ) => {
// { code: string, message: string, cause?: {} }
console.log( "error", error );
// If there is no error code, log the error
@@ -177,22 +180,23 @@ const CameraView = ( {
<ReanimatedCamera
// Shared props between StandardCamera and AICamera
ref={cameraRef}
animatedProps={animatedProps}
device={device}
format={format}
exposure={exposure}
isActive={isFocused}
style={StyleSheet.absoluteFill}
photo
enableZoomGesture={false}
onError={e => onError( e )}
exposure={exposure}
format={format}
frameProcessor={frameProcessor}
isActive={isActive}
onError={onError}
// react-native-vision-camera v3.9.0: This prop is undocumented, but does work on iOS
// it does nothing on Android so we set it to null there
orientation={orientationPatch( deviceOrientation )}
photo
photoQualityBalance="quality"
frameProcessor={frameProcessor}
pixelFormat="yuv"
animatedProps={animatedProps}
resizeMode={resizeMode || "cover"}
style={StyleSheet.absoluteFill}
/>
</GestureDetector>
<FocusSquare

View File

@@ -1,5 +1,3 @@
// @flow
import { useNavigation } from "@react-navigation/native";
import LocationPermissionGate from "components/SharedComponents/LocationPermissionGate";
import PermissionGateContainer, { WRITE_MEDIA_PERMISSIONS }
@@ -12,6 +10,7 @@ import React, {
import { StatusBar } from "react-native";
import DeviceInfo from "react-native-device-info";
import Orientation from "react-native-orientation-locker";
import { Camera } from "react-native-vision-camera";
import { useTranslation } from "sharedHooks";
import useDeviceOrientation, {
LANDSCAPE_LEFT,
@@ -24,8 +23,8 @@ import StandardCamera from "./StandardCamera/StandardCamera";
const isTablet = DeviceInfo.isTablet( );
type Props = {
addEvidence: ?boolean,
interface Props {
addEvidence: boolean,
cameraType: string,
cameraPosition: string,
device: Object,
@@ -45,7 +44,7 @@ const CameraWithDevice = ( {
}
const navigation = useNavigation();
const { t } = useTranslation( );
const camera = useRef( null );
const camera = useRef<Camera>( null );
const { deviceOrientation } = useDeviceOrientation( );
const [addPhotoPermissionResult, setAddPhotoPermissionResult] = useState( null );
const [checkmarkTapped, setCheckmarkTapped] = useState( false );

View File

@@ -2,11 +2,11 @@
import { useFocusEffect, useNavigation, useRoute } from "@react-navigation/native";
import classnames from "classnames";
import CameraView from "components/Camera/CameraView";
import CameraView from "components/Camera/CameraView.tsx";
import FadeInOutView from "components/Camera/FadeInOutView";
import useRotation from "components/Camera/hooks/useRotation";
import useTakePhoto from "components/Camera/hooks/useTakePhoto";
import useZoom from "components/Camera/hooks/useZoom";
import useTakePhoto from "components/Camera/hooks/useTakePhoto.ts";
import useZoom from "components/Camera/hooks/useZoom.ts";
import navigateToObsDetails from "components/ObsDetails/helpers/navigateToObsDetails";
import { View } from "components/styledComponents";
import { getCurrentRoute } from "navigation/navigationUtils";

View File

@@ -1,8 +1,7 @@
// @flow
import {
useState
} from "react";
import type { PhotoFile } from "react-native-vision-camera";
import Photo from "realmModels/Photo";
import {
rotatePhotoPatch,
@@ -12,7 +11,7 @@ import {
import useDeviceOrientation from "sharedHooks/useDeviceOrientation";
import useStore from "stores/useStore";
const useTakePhoto = ( camera: Object, addEvidence: ?boolean, device: Object ): Object => {
const useTakePhoto = ( camera: Object, addEvidence: boolean, device: Object ): Object => {
const currentObservation = useStore( state => state.currentObservation );
const { deviceOrientation } = useDeviceOrientation( );
const hasFlash = device?.hasFlash;
@@ -30,7 +29,7 @@ const useTakePhoto = ( camera: Object, addEvidence: ?boolean, device: Object ):
const takePhoto = async ( ) => {
setTakingPhoto( true );
const cameraPhoto = await camera.current.takePhoto( takePhotoOptions );
const cameraPhoto: PhotoFile = await camera.current.takePhoto( takePhotoOptions );
// Rotate the original photo depending on device orientation
const photoRotation = rotationTempPhotoPatch( cameraPhoto, deviceOrientation );

View File

@@ -1,5 +1,3 @@
// @flow
import {
useState
} from "react";
@@ -10,6 +8,7 @@ import {
useSharedValue,
withSpring
} from "react-native-reanimated";
import type { CameraProps } from "react-native-vision-camera";
// This is taken from react-native-vision library itself: https://github.com/mrousavy/react-native-vision-camera/blob/9eed89aac6155eba155595f3e006707152550d0d/package/example/src/Constants.ts#L19 https://github.com/mrousavy/react-native-vision-camera/blob/9eed89aac6155eba155595f3e006707152550d0d/package/example/src/CameraPage.tsx#L34
// The maximum zoom factor you should be able to zoom in
@@ -67,7 +66,7 @@ const useZoom = ( device: Object ): Object => {
zoom.value = newZoom;
};
const animatedProps = useAnimatedProps(
const animatedProps = useAnimatedProps < CameraProps >(
() => ( { zoom: zoom.value } ),
[zoom]
);

View File

@@ -2,7 +2,7 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import CameraContainer from "components/Camera/CameraContainer";
import CameraContainer from "components/Camera/CameraContainer.tsx";
import GroupPhotosContainer from "components/PhotoImporter/GroupPhotosContainer";
import PhotoGallery from "components/PhotoImporter/PhotoGallery";
import { Heading4 } from "components/SharedComponents";

View File

@@ -49,7 +49,7 @@ jest.mock( "components/Camera/AICamera/hooks/usePredictions", () => ( {
default: () => mockModelLoaded
} ) );
jest.mock( "components/Camera/hooks/useZoom", () => ( {
jest.mock( "components/Camera/hooks/useZoom.ts", () => ( {
__esModule: true,
default: () => ( {
animatedProps: {}

View File

@@ -1,7 +1,7 @@
import {
fireEvent, render, screen
} from "@testing-library/react-native";
import CameraContainer from "components/Camera/CameraContainer";
import CameraContainer from "components/Camera/CameraContainer.tsx";
import INatPaperProvider from "providers/INatPaperProvider";
import React from "react";
import { View } from "react-native";
@@ -32,7 +32,7 @@ jest.mock( "@react-navigation/native", () => {
} );
const mockView = <View />;
jest.mock( "components/Camera/CameraView", () => ( {
jest.mock( "components/Camera/CameraView.tsx", () => ( {
__esModule: true,
default: ( ) => mockView
} ) );