mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-05-18 05:22:30 -04:00
Enable multi-camera (#1461)
* Use multi-cam; closes #1458 * Typescript conversion
This commit is contained in:
committed by
GitHub
parent
f10b25fecc
commit
2b1bcd9363
@@ -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
|
||||
|
||||
@@ -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
10
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
@@ -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";
|
||||
|
||||
@@ -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 );
|
||||
@@ -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]
|
||||
);
|
||||
@@ -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";
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
} ) );
|
||||
|
||||
Reference in New Issue
Block a user