Merge branch 'master' into loewy/rn-navigation

This commit is contained in:
Loewy 2024-02-01 11:42:50 -08:00
commit c882786726
18 changed files with 518 additions and 194 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*.json]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
/react-native-vision-camera/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "react-native-vision-camera"]
path = react-native-vision-camera
url = https://dev.railbird.ai/railbird/react-native-vision-camera.git

View File

@ -174,9 +174,11 @@ dependencies {
} else { } else {
implementation jscFlavor implementation jscFlavor
} }
implementation project(':react-native-vision-camera')
} }
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesAppBuildGradle(project) applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

View File

@ -15,6 +15,7 @@ import com.facebook.soloader.SoLoader;
import expo.modules.ApplicationLifecycleDispatcher; import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper; import expo.modules.ReactNativeHostWrapper;
import com.mrousavy.camera.CameraPackage;
import java.util.List; import java.util.List;
@ -32,7 +33,8 @@ public class MainApplication extends Application implements ReactApplication {
@SuppressWarnings("UnnecessaryLocalVariable") @SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages(); List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example: // Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage()); // packages.add(new MyReactNativePackage());
packages.add(new CameraPackage());
return packages; return packages;
} }

View File

@ -20,6 +20,7 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
classpath('com.android.tools.build:gradle:7.4.2') classpath('com.android.tools.build:gradle:7.4.2')
classpath('com.facebook.react:react-native-gradle-plugin') classpath('com.facebook.react:react-native-gradle-plugin')
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.22")
} }
} }

View File

@ -1,10 +1,17 @@
rootProject.name = 'Railbird' rootProject.name = 'Railbird'
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle");
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
useExpoModules() useExpoModules()
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesSettingsGradle(settings) applyNativeModulesSettingsGradle(settings)
include ':react-native-vision-camera'
project(':react-native-vision-camera').projectDir = new File(rootProject.projectDir, '../react-native-vision-camera/package/android')
include ':app' include ':app'
includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile()) includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile())

View File

@ -11,9 +11,7 @@
"resizeMode": "contain", "resizeMode": "contain",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"assetBundlePatterns": [ "assetBundlePatterns": ["**/*"],
"**/*"
],
"plugins": [ "plugins": [
"@react-native-firebase/app", "@react-native-firebase/app",
"@react-native-firebase/auth", "@react-native-firebase/auth",

View File

@ -1,16 +1,34 @@
module.exports = function(api) { const path = require("path");
api.cache(true); const pak = require("./react-native-vision-camera/package/package.json");
return {
presets: ["babel-preset-expo"], module.exports = function (api) {
plugins: [ api.cache(true);
["module:react-native-dotenv", { return {
"moduleName": "@env", presets: ["babel-preset-expo"],
"path": ".env", plugins: [
"safe": false, [
"allowUndefined": true, "module:react-native-dotenv",
"verbose": false, {
}], moduleName: "@env",
'react-native-reanimated/plugin', path: ".env",
], safe: false,
}; allowUndefined: true,
verbose: false,
},
],
"react-native-reanimated/plugin",
[
"module-resolver",
{
alias: {
[pak.name]: path.join(
__dirname,
"./react-native-vision-camera/package",
pak.source,
),
},
},
],
],
};
}; };

View File

@ -1,51 +1,71 @@
import React, { useCallback, useRef, useState } from 'react' import React, { useCallback, useRef, useState } from "react";
import { StyleSheet, Text, View } from 'react-native' import { Button, StyleSheet, Text, View } from "react-native";
import { Camera, useCameraPermission, useCameraDevice, useCameraFormat, PhotoFile, VideoFile, CameraRuntimeError } from 'react-native-vision-camera' import {
import { RecordingButton } from './capture-button' Camera,
import { useIsForeground } from './is-foreground' useCameraPermission,
useCameraDevice,
useCameraFormat,
PhotoFile,
VideoFile,
CameraRuntimeError,
Orientation,
// @ts-ignore
} from "react-native-vision-camera";
import { RecordingButton } from "./capture-button";
import { useIsForeground } from "./is-foreground";
import { useIsFocused } from '@react-navigation/native' import { useIsFocused } from '@react-navigation/native'
export default function CameraScreen(): React.ReactElement { export default function CameraScreen(): React.ReactElement {
const camera = useRef<Camera>(null) const camera = useRef<Camera>(null);
const { hasPermission, requestPermission } = useCameraPermission() const { hasPermission, requestPermission } = useCameraPermission();
const [isCameraInitialized, setIsCameraInitialized] = useState(false) const [isCameraInitialized, setIsCameraInitialized] =
useState<boolean>(false);
const isForeground = useIsForeground() const isForeground = useIsForeground();
const isFocused = useIsFocused(); const isFocused = useIsFocused();
const isActive = isForeground && isFocused const isActive = isForeground && isFocused;
const onError = useCallback((error: CameraRuntimeError) => { const onError = useCallback((error: CameraRuntimeError) => {
console.error(error) console.error(error);
}, []) }, []);
const onInitialized = useCallback(() => { const onInitialized = useCallback(() => {
console.log('Camera initialized!') console.log("Camera initialized!");
setIsCameraInitialized(true) setIsCameraInitialized(true);
}, []) }, []);
const onMediaCaptured = useCallback( const onMediaCaptured = useCallback((media: PhotoFile | VideoFile) => {
(media: PhotoFile | VideoFile) => { console.log(`Media captured! ${JSON.stringify(media)}`);
console.log(`Media captured! ${JSON.stringify(media)}`) }, []);
},
[],
)
if (!hasPermission) { if (!hasPermission) {
requestPermission() requestPermission();
// Error handling in case they refuse to give permission // Error handling in case they refuse to give permission
} }
const device = useCameraDevice('back') const device = useCameraDevice("back");
const format = useCameraFormat(device, [ const format = useCameraFormat(device, [
{ videoResolution: { width: 3048, height: 2160 } }, { videoResolution: { width: 3048, height: 2160 } },
{ fps: 60 } { fps: 60 },
]) ]); // this sets as a target
//Orientation detection
const [orientation, setOrientation] = useState<Orientation>("portrait");
const toggleOrientation = () => {
setOrientation(
(currentOrientation) =>
currentOrientation === "landscape-left" ? "portrait" : "landscape-left", // Can adjust this and the type to match what we want
);
};
if (device === null) { if (device === null) {
return <Text>Camera not available. Does user have permissions: {hasPermission}</Text> return (
<Text>
Camera not available. Does user have permissions: {hasPermission}
</Text>
);
} }
return ( return (
hasPermission && ( hasPermission && (
<View style={styles.container}> <View style={styles.container}>
@ -57,30 +77,64 @@ export default function CameraScreen(): React.ReactElement {
onInitialized={onInitialized} onInitialized={onInitialized}
onError={onError} onError={onError}
video={true} video={true}
photo={false} orientation={orientation} // TODO: #60
orientation='portrait' // TODO: #60
isActive={isActive} isActive={isActive}
/> />
<RecordingButton <RecordingButton
style={styles.captureButton} style={[
styles.captureButton,
orientation === "portrait" ? styles.portrait : styles.landscape,
]}
camera={camera} camera={camera}
onMediaCaptured={onMediaCaptured} onMediaCaptured={onMediaCaptured}
enabled={isCameraInitialized} enabled={isCameraInitialized}
/> />
<View
style={[
styles.button,
orientation === "portrait"
? styles.togglePortrait
: styles.toggleLandscape,
]}
>
<Button
title="Toggle Orientation"
onPress={toggleOrientation}
color="#841584"
accessibilityLabel="Toggle camera orientation"
/>
</View>
</View> </View>
) )
) );
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: 'black', backgroundColor: "black",
}, },
captureButton: { captureButton: {
position: 'absolute', position: "absolute",
alignSelf: 'center', alignSelf: "center",
bottom: 20, // Should come from SafeAreaProvider
}, },
}) button: {
position: "absolute",
alignSelf: "center",
},
togglePortrait: {
bottom: 110, // needs refined
},
toggleLandscape: {
transform: [{ rotate: "90deg" }],
bottom: "43%", // Should come from SafeAreaProvider, hardcoded right now, should roughly appear above the button
left: 50, // needs refined
},
portrait: {
bottom: 20, // needs refined
},
landscape: {
bottom: "40%", // Should come from SafeAreaProvider
left: 20, // needs refined
},
});

View File

@ -1,68 +1,89 @@
import React, { useCallback, useRef, useState } from 'react'; import React, { useCallback, useRef, useState } from "react";
import { TouchableOpacity, StyleSheet, View } from 'react-native'; import {
TouchableOpacity,
StyleSheet,
View,
StyleProp,
ViewStyle,
} from "react-native";
import { CameraRoll } from "@react-native-camera-roll/camera-roll"; import { CameraRoll } from "@react-native-camera-roll/camera-roll";
// @ts-ignore
import { Camera } from "react-native-vision-camera/lib/typescript/Camera";
// @ts-ignore
import { VideoFile } from "react-native-vision-camera/lib/typescript/VideoFile";
interface RecordingButtonProps {
style: StyleProp<ViewStyle>;
camera: React.RefObject<Camera>;
// eslint-disable-next-line no-unused-vars
onMediaCaptured: (media: VideoFile, mediaType: string) => void;
enabled: boolean;
}
export const RecordingButton = ({ style, camera, onMediaCaptured, enabled }) => { export const RecordingButton: React.FC<RecordingButtonProps> = ({
style,
const isRecording = useRef(false) camera,
onMediaCaptured,
enabled,
}) => {
const isRecording = useRef(false);
// UseRef won't trigger a re-render // UseRef won't trigger a re-render
const [, setRecordingState] = useState(false); const [, setRecordingState] = useState(false);
const onStoppedRecording = useCallback(() => { const onStoppedRecording = useCallback(() => {
isRecording.current = false isRecording.current = false;
setRecordingState(false) setRecordingState(false);
console.log('stopped recording video!') console.log("stopped recording video!");
}, []) }, []);
const stopRecording = useCallback(async () => { const stopRecording = useCallback(async () => {
try { try {
if (camera.current === null) { if (camera.current === null) {
throw new Error('Camera ref is null!') // Error handling could be more graceful throw new Error("Camera ref is null!"); // Error handling could be more graceful
} }
console.log('calling stopRecording()...') console.log("calling stopRecording()...");
await camera.current.stopRecording() await camera.current.stopRecording();
console.log('called stopRecording()!') console.log("called stopRecording()!");
} catch (e) { } catch (e) {
console.error('failed to stop recording!', e) console.error("failed to stop recording!", e);
} }
}, [camera]) }, [camera]);
const startRecording = useCallback(() => { const startRecording = useCallback(() => {
try { try {
if (camera.current === null) { if (camera.current === null) {
throw new Error('Camera ref is null!') // Error handling could be more graceful throw new Error("Camera ref is null!"); // Error handling could be more graceful
} }
console.log('calling startRecording()...') console.log("calling startRecording()...");
camera.current.startRecording({ camera.current.startRecording({
onRecordingError: (error) => { onRecordingError: (error) => {
console.error('Recording failed!', error) console.error("Recording failed!", error);
onStoppedRecording() onStoppedRecording();
}, },
onRecordingFinished: async (video) => { onRecordingFinished: async (video) => {
onMediaCaptured(video, 'video') onMediaCaptured(video, "video");
const path = video.path const path = video.path;
await CameraRoll.saveAsset(`file://${path}`, { await CameraRoll.saveAsset(`file://${path}`, {
type: 'video', type: "video",
}) });
onStoppedRecording() onStoppedRecording();
}, },
}) });
console.log('called startRecording()!') console.log("called startRecording()!");
isRecording.current = true isRecording.current = true;
setRecordingState(true) setRecordingState(true);
} catch (e) { } catch (e) {
console.error('failed to start recording!', e, 'camera') console.error("failed to start recording!", e, "camera");
} }
}, [camera, onMediaCaptured, onStoppedRecording]) }, [camera, onMediaCaptured, onStoppedRecording]);
const handlePress = () => { const handlePress = () => {
if (isRecording.current) { if (isRecording.current) {
stopRecording(); stopRecording();
} else { } else {
startRecording(); startRecording();
} }
}; };
return ( return (
<TouchableOpacity <TouchableOpacity
@ -70,34 +91,38 @@ const handlePress = () => {
onPress={handlePress} onPress={handlePress}
disabled={!enabled} disabled={!enabled}
> >
<View style={isRecording.current ? styles.recordingSquare : styles.innerCircle} /> <View
style={
isRecording.current ? styles.recordingSquare : styles.innerCircle
}
/>
</TouchableOpacity> </TouchableOpacity>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
captureButton: { captureButton: {
height: 80, height: 80,
width: 80, width: 80,
borderRadius: 40, borderRadius: 40,
borderWidth: 3, borderWidth: 3,
borderColor: 'white', borderColor: "white",
backgroundColor: 'transparent', backgroundColor: "transparent",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
}, },
innerCircle: { innerCircle: {
height: 70, height: 70,
width: 70, width: 70,
borderRadius: 35, borderRadius: 35,
backgroundColor: '#FF3B30', backgroundColor: "#FF3B30",
}, },
recordingSquare: { recordingSquare: {
height: 40, height: 40,
width: 40, width: 40,
borderRadius: 10, borderRadius: 10,
backgroundColor: '#FF3B30', backgroundColor: "#FF3B30",
}, },
}); });
export default RecordingButton; export default RecordingButton;

112
flake.nix
View File

@ -11,56 +11,64 @@
url = "github:tadfisher/android-nixpkgs"; url = "github:tadfisher/android-nixpkgs";
}; };
}; };
outputs = { self, nixpkgs, flake-utils, gitignore, android-nixpkgs, ... }: outputs = {
flake-utils.lib.eachDefaultSystem (system: self,
let nixpkgs,
pkgs = import nixpkgs { inherit system; }; flake-utils,
nodejs = pkgs.nodejs-18_x; gitignore,
# NOTE: this does not work android-nixpkgs,
appBuild = pkgs.stdenv.mkDerivation { ...
name = "example-ts-node"; }:
version = "0.1.0"; flake-utils.lib.eachDefaultSystem (system: let
src = gitignore.lib.gitignoreSource ./.; # uses the gitignore in the repo to only copy files git would see pkgs = import nixpkgs {inherit system;};
buildInputs = [ nodejs ]; nodejs = pkgs.nodejs-18_x;
# https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases # NOTE: this does not work
buildPhase = '' appBuild = pkgs.stdenv.mkDerivation {
# each phase has pre/postHooks. When you make your own phase be sure to still call the hooks name = "example-ts-node";
runHook preBuild version = "0.1.0";
npm ci src = gitignore.lib.gitignoreSource ./.; # uses the gitignore in the repo to only copy files git would see
npm run build buildInputs = [nodejs];
runHook postBuild # https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases
''; buildPhase = ''
installPhase = '' # each phase has pre/postHooks. When you make your own phase be sure to still call the hooks
runHook preInstall runHook preBuild
cp -r node_modules $out/node_modules npm ci
cp package.json $out/package.json npm run build
cp -r dist $out/dist runHook postBuild
runHook postInstall '';
''; installPhase = ''
}; runHook preInstall
android-sdk = android-nixpkgs.sdk.${system} (sdkPkgs: with sdkPkgs; [ cp -r node_modules $out/node_modules
cmdline-tools-latest cp package.json $out/package.json
build-tools-30-0-3 cp -r dist $out/dist
build-tools-33-0-0 runHook postInstall
platform-tools '';
platforms-android-33 };
emulator android-sdk = android-nixpkgs.sdk.${system} (sdkPkgs:
ndk-23-1-7779620 with sdkPkgs; [
cmake-3-22-1 cmdline-tools-latest
system-images-android-33-google-apis-x86-64 build-tools-30-0-3
system-images-android-34-google-apis-x86-64 build-tools-33-0-0
]); platform-tools
in with pkgs; { platforms-android-33
defaultPackage = appBuild; emulator
devShell = mkShell { ndk-23-1-7779620
buildInputs = [ nodejs yarn watchman gradle_7 alejandra nodePackages.prettier ]; cmake-3-22-1
ANDROID_SDK_BIN = android-sdk; system-images-android-33-google-apis-x86-64
shellHook = '' system-images-android-34-google-apis-x86-64
export JAVA_HOME=${pkgs.jdk17.home} ]);
source ${android-sdk.out}/nix-support/setup-hook in
export PATH=${android-sdk}/bin:$PATH with pkgs; {
ORG_GRADLE_PROJECT_ANDROID_HOME="$ANDROID_HOME" defaultPackage = appBuild;
''; devShell = mkShell {
}; buildInputs = [nodejs yarn watchman gradle_7 alejandra nodePackages.prettier];
}); ANDROID_SDK_BIN = android-sdk;
shellHook = ''
export JAVA_HOME=${pkgs.jdk17.home}
source ${android-sdk.out}/nix-support/setup-hook
export PATH=${android-sdk}/bin:${android-sdk}/share/android-sdk/build-tools/33.0.0/:$PATH
ORG_GRADLE_PROJECT_ANDROID_HOME="$ANDROID_HOME"
'';
};
});
} }

View File

@ -384,7 +384,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = ai.railbird.railbird; PRODUCT_BUNDLE_IDENTIFIER = ai.railbird.railbird;
PRODUCT_NAME = Railbird; PRODUCT_NAME = "Railbird";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Railbird/Railbird-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Railbird/Railbird-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -419,7 +419,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = ai.railbird.railbird; PRODUCT_BUNDLE_IDENTIFIER = ai.railbird.railbird;
PRODUCT_NAME = Railbird; PRODUCT_NAME = "Railbird";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Railbird/Railbird-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Railbird/Railbird-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@ -1,7 +1,53 @@
// Learn more https://docs.expo.io/guides/customizing-metro const { getDefaultConfig } = require("expo/metro-config");
const { getDefaultConfig } = require('expo/metro-config'); const { mergeConfig } = require("@react-native/metro-config");
const path = require("path");
const escape = require("escape-string-regexp");
const exclusionList = require("metro-config/src/defaults/exclusionList");
const pak = require("./react-native-vision-camera/package/package.json");
/** @type {import('expo/metro-config').MetroConfig} */ const root = path.resolve(__dirname, "./react-native-vision-camera/package");
const config = getDefaultConfig(__dirname); const modules = Object.keys({ ...pak.peerDependencies });
const localPackagePath = path.resolve(
__dirname,
"./react-native-vision-camera/package/src/index",
);
module.exports = config; /**
* Metro configuration
* https://facebook.github.io/metro/docs/configuration
*
* @type {import('metro-config').MetroConfig}
*/
const config = {
watchFolders: [root],
// We need to make sure that only one version is loaded for peerDependencies
// So we block them at the root, and alias them to the versions in example's node_modules
resolver: {
blacklistRE: exclusionList(
modules.map(
(m) =>
new RegExp(`^${escape(path.join(root, "node_modules", m))}\\/.*$`),
),
),
extraNodeModules: modules.reduce(
(acc, name) => {
acc[name] = path.join(__dirname, "node_modules", name);
return acc;
},
{ "react-native-vision-camera": localPackagePath },
),
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

View File

@ -4,10 +4,10 @@
"main": "node_modules/expo/AppEntry.js", "main": "node_modules/expo/AppEntry.js",
"scripts": { "scripts": {
"start": "cp .env.development .env && expo start", "start": "cp .env.development .env && expo start",
"android": "expo start --android", "start:android": "expo start --android",
"ios": "expo start --ios", "start:ios": "expo start --ios",
"run:android": "expo run:android", "android": "expo run:android",
"run:ios": "expo run:ios", "ios": "expo run:ios",
"web": "expo start --web", "web": "expo start --web",
"lint": "eslint . --ext .js,.ts,.tsx", "lint": "eslint . --ext .js,.ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix", "lint:fix": "eslint . --ext .ts,.tsx --fix",
@ -31,6 +31,7 @@
"@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0", "@typescript-eslint/parser": "^6.17.0",
"babel-plugin-inline-dotenv": "^1.7.0", "babel-plugin-inline-dotenv": "^1.7.0",
"expo-constants": "15.4.5",
"d3-path": "^3.1.0", "d3-path": "^3.1.0",
"d3-scale": "^1.0.6", "d3-scale": "^1.0.6",
"eslint": "^8.56.0", "eslint": "^8.56.0",
@ -53,15 +54,16 @@
"react-native-static-safe-area-insets": "^2.2.0", "react-native-static-safe-area-insets": "^2.2.0",
"react-native-svg": "13.9.0", "react-native-svg": "13.9.0",
"react-native-svg-charts": "^5.4.0", "react-native-svg-charts": "^5.4.0",
"react-native-vision-camera": "^3.8.2",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",
"@testing-library/jest-native": "^5.4.3", "@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^12.4.3", "@testing-library/react-native": "^12.4.3",
"@react-native/metro-config": "^0.72.9",
"@types/d3-path": "^3.0.2", "@types/d3-path": "^3.0.2",
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
"metro-react-native-babel-preset": "^0.77.0",
"@types/react-native-svg-charts": "^5.0.16", "@types/react-native-svg-charts": "^5.0.16",
"eslint-config-prettier": "^9.1.0" "eslint-config-prettier": "^9.1.0"
}, },

@ -0,0 +1 @@
Subproject commit fb425458904eb240466768be08352973fd2f78d8

View File

@ -1,4 +1,5 @@
{ {
"compilerOptions": {}, "include": ["."],
"extends": "expo/tsconfig.base" "exclude": ["node_modules", "./react-native-vision-camera/package"],
"extends": ["expo/tsconfig.base"]
} }

158
yarn.lock
View File

@ -1400,11 +1400,39 @@
xcode "^3.0.1" xcode "^3.0.1"
xml2js "0.6.0" xml2js "0.6.0"
"@expo/config-plugins@~7.8.2":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.8.4.tgz#533b5d536c1dc8b5544d64878b51bda28f2e1a1f"
integrity sha512-hv03HYxb/5kX8Gxv/BTI8TLc9L06WzqAfHRRXdbar4zkLcP2oTzvsLEF4/L/TIpD3rsnYa0KU42d0gWRxzPCJg==
dependencies:
"@expo/config-types" "^50.0.0-alpha.1"
"@expo/fingerprint" "^0.6.0"
"@expo/json-file" "~8.3.0"
"@expo/plist" "^0.1.0"
"@expo/sdk-runtime-versions" "^1.0.0"
"@react-native/normalize-color" "^2.0.0"
chalk "^4.1.2"
debug "^4.3.1"
find-up "~5.0.0"
getenv "^1.0.0"
glob "7.1.6"
resolve-from "^5.0.0"
semver "^7.5.3"
slash "^3.0.0"
slugify "^1.6.6"
xcode "^3.0.1"
xml2js "0.6.0"
"@expo/config-types@^49.0.0-alpha.1": "@expo/config-types@^49.0.0-alpha.1":
version "49.0.0" version "49.0.0"
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-49.0.0.tgz#15ffef715285c06703f6fb7ec0cda853f645cc09" resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-49.0.0.tgz#15ffef715285c06703f6fb7ec0cda853f645cc09"
integrity sha512-8eyREVi+K2acnMBe/rTIu1dOfyR2+AMnTLHlut+YpMV9OZPdeKV0Bs9BxAewGqBA2slslbQ9N39IS2CuTKpXkA== integrity sha512-8eyREVi+K2acnMBe/rTIu1dOfyR2+AMnTLHlut+YpMV9OZPdeKV0Bs9BxAewGqBA2slslbQ9N39IS2CuTKpXkA==
"@expo/config-types@^50.0.0", "@expo/config-types@^50.0.0-alpha.1":
version "50.0.0"
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-50.0.0.tgz#b534d3ec997ec60f8af24f6ad56244c8afc71a0b"
integrity sha512-0kkhIwXRT6EdFDwn+zTg9R2MZIAEYGn1MVkyRohAd+C9cXOb5RA8WLQi7vuxKF9m1SMtNAUrf0pO+ENK0+/KSw==
"@expo/config@8.1.2", "@expo/config@~8.1.0": "@expo/config@8.1.2", "@expo/config@~8.1.0":
version "8.1.2" version "8.1.2"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-8.1.2.tgz#7fff28b3acefe39702e9f3ce1c9fd896a52caa80" resolved "https://registry.yarnpkg.com/@expo/config/-/config-8.1.2.tgz#7fff28b3acefe39702e9f3ce1c9fd896a52caa80"
@ -1422,6 +1450,23 @@
slugify "^1.3.4" slugify "^1.3.4"
sucrase "^3.20.0" sucrase "^3.20.0"
"@expo/config@~8.5.0":
version "8.5.4"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-8.5.4.tgz#bb5eb06caa36e4e35dc8c7647fae63e147b830ca"
integrity sha512-ggOLJPHGzJSJHVBC1LzwXwR6qUn8Mw7hkc5zEKRIdhFRuIQ6s2FE4eOvP87LrNfDF7eZGa6tJQYsiHSmZKG+8Q==
dependencies:
"@babel/code-frame" "~7.10.4"
"@expo/config-plugins" "~7.8.2"
"@expo/config-types" "^50.0.0"
"@expo/json-file" "^8.2.37"
getenv "^1.0.0"
glob "7.1.6"
require-from-string "^2.0.2"
resolve-from "^5.0.0"
semver "7.5.3"
slugify "^1.3.4"
sucrase "3.34.0"
"@expo/dev-server@0.5.5": "@expo/dev-server@0.5.5":
version "0.5.5" version "0.5.5"
resolved "https://registry.yarnpkg.com/@expo/dev-server/-/dev-server-0.5.5.tgz#33f9065e0cf5f36ac61944a92d11390cc71b7035" resolved "https://registry.yarnpkg.com/@expo/dev-server/-/dev-server-0.5.5.tgz#33f9065e0cf5f36ac61944a92d11390cc71b7035"
@ -1473,6 +1518,19 @@
dotenv-expand "~10.0.0" dotenv-expand "~10.0.0"
getenv "^1.0.0" getenv "^1.0.0"
"@expo/fingerprint@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.6.0.tgz#77366934673d4ecea37284109b4dd67f9e6a7487"
integrity sha512-KfpoVRTMwMNJ/Cf5o+Ou8M/Y0EGSTqK+rbi70M2Y0K2qgWNfMJ1gm6sYO9uc8lcTr7YSYM1Rme3dk7QXhpScNA==
dependencies:
"@expo/spawn-async" "^1.5.0"
chalk "^4.1.2"
debug "^4.3.4"
find-up "^5.0.0"
minimatch "^3.0.4"
p-limit "^3.1.0"
resolve-from "^5.0.0"
"@expo/image-utils@0.3.22": "@expo/image-utils@0.3.22":
version "0.3.22" version "0.3.22"
resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.22.tgz#3a45fb2e268d20fcc761c87bca3aca7fd8e24260" resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.22.tgz#3a45fb2e268d20fcc761c87bca3aca7fd8e24260"
@ -1499,6 +1557,15 @@
json5 "^2.2.2" json5 "^2.2.2"
write-file-atomic "^2.3.0" write-file-atomic "^2.3.0"
"@expo/json-file@~8.3.0":
version "8.3.0"
resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.3.0.tgz#fc84af77b532a4e9bfb5beafd0e3b7f692b6bd7e"
integrity sha512-yROUeXJXR5goagB8c3muFLCzLmdGOvoPpR5yDNaXrnTp4euNykr9yW0wWhJx4YVRTNOPtGBnEbbJBW+a9q+S6g==
dependencies:
"@babel/code-frame" "~7.10.4"
json5 "^2.2.2"
write-file-atomic "^2.3.0"
"@expo/metro-config@~0.10.0": "@expo/metro-config@~0.10.0":
version "0.10.7" version "0.10.7"
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.10.7.tgz#d1b91baffcb7feb52fc7e2e122450bfc5d01e7c1" resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.10.7.tgz#d1b91baffcb7feb52fc7e2e122450bfc5d01e7c1"
@ -1551,6 +1618,15 @@
base64-js "^1.2.3" base64-js "^1.2.3"
xmlbuilder "^14.0.0" xmlbuilder "^14.0.0"
"@expo/plist@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.1.0.tgz#eabc95f951d14e10c87fd0443ee01d567371f058"
integrity sha512-xWD+8vIFif0wKyuqe3fmnmnSouXYucciZXFzS0ZD5OV9eSAS1RGQI5FaGGJ6zxJ4mpdy/4QzbLdBjnYE5vxA0g==
dependencies:
"@xmldom/xmldom" "~0.7.7"
base64-js "^1.2.3"
xmlbuilder "^14.0.0"
"@expo/prebuild-config@6.2.6": "@expo/prebuild-config@6.2.6":
version "6.2.6" version "6.2.6"
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-6.2.6.tgz#c5b4f8adcba4be00c874d6b24a8267d45c555261" resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-6.2.6.tgz#c5b4f8adcba4be00c874d6b24a8267d45c555261"
@ -2487,6 +2563,16 @@
hermes-parser "0.15.0" hermes-parser "0.15.0"
nullthrows "^1.1.1" nullthrows "^1.1.1"
"@react-native/metro-config@^0.72.9":
version "0.72.11"
resolved "https://registry.yarnpkg.com/@react-native/metro-config/-/metro-config-0.72.11.tgz#c775a22fbb138cedd4513ca46c06bfd6a9dad316"
integrity sha512-661EyQnDdVelyc0qP/ew7kKkGAh6N6KlkuPLC2SQ8sxaXskVU6fSuNlpLW4bUTBUDFKG8gEOU2hp6rzk4wQnGQ==
dependencies:
"@react-native/js-polyfills" "^0.72.1"
metro-config "0.76.8"
metro-react-native-babel-transformer "0.76.8"
metro-runtime "0.76.8"
"@react-native/normalize-color@^2.0.0": "@react-native/normalize-color@^2.0.0":
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.1.0.tgz#939b87a9849e81687d3640c5efa2a486ac266f91" resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.1.0.tgz#939b87a9849e81687d3640c5efa2a486ac266f91"
@ -4914,6 +5000,13 @@ expo-build-properties@^0.11.0:
ajv "^8.11.0" ajv "^8.11.0"
semver "^7.5.3" semver "^7.5.3"
expo-constants@15.4.5:
version "15.4.5"
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-15.4.5.tgz#81756a4c4e1c020f840a419cd86a124a6d1fb35b"
integrity sha512-1pVVjwk733hbbIjtQcvUFCme540v4gFemdNlaxM2UXKbfRCOh2hzgKN5joHMOysoXQe736TTUrRj7UaZI5Yyhg==
dependencies:
"@expo/config" "~8.5.0"
expo-constants@~14.4.2: expo-constants@~14.4.2:
version "14.4.2" version "14.4.2"
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-14.4.2.tgz#cac5e8b524069545739b8d8595ce96cc5be6578c" resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-14.4.2.tgz#cac5e8b524069545739b8d8595ce96cc5be6578c"
@ -7283,6 +7376,51 @@ metro-react-native-babel-preset@0.76.8:
babel-plugin-transform-flow-enums "^0.0.2" babel-plugin-transform-flow-enums "^0.0.2"
react-refresh "^0.4.0" react-refresh "^0.4.0"
metro-react-native-babel-preset@^0.77.0:
version "0.77.0"
resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.77.0.tgz#47457eca8e36b77156afbe790247a70dbb40faaa"
integrity sha512-HPPD+bTxADtoE4y/4t1txgTQ1LVR6imOBy7RMHUsqMVTbekoi8Ph5YI9vKX2VMPtVWeFt0w9YnCSLPa76GcXsA==
dependencies:
"@babel/core" "^7.20.0"
"@babel/plugin-proposal-async-generator-functions" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.18.0"
"@babel/plugin-proposal-export-default-from" "^7.0.0"
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.0"
"@babel/plugin-proposal-numeric-separator" "^7.0.0"
"@babel/plugin-proposal-object-rest-spread" "^7.20.0"
"@babel/plugin-proposal-optional-catch-binding" "^7.0.0"
"@babel/plugin-proposal-optional-chaining" "^7.20.0"
"@babel/plugin-syntax-dynamic-import" "^7.8.0"
"@babel/plugin-syntax-export-default-from" "^7.0.0"
"@babel/plugin-syntax-flow" "^7.18.0"
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0"
"@babel/plugin-syntax-optional-chaining" "^7.0.0"
"@babel/plugin-transform-arrow-functions" "^7.0.0"
"@babel/plugin-transform-async-to-generator" "^7.20.0"
"@babel/plugin-transform-block-scoping" "^7.0.0"
"@babel/plugin-transform-classes" "^7.0.0"
"@babel/plugin-transform-computed-properties" "^7.0.0"
"@babel/plugin-transform-destructuring" "^7.20.0"
"@babel/plugin-transform-flow-strip-types" "^7.20.0"
"@babel/plugin-transform-function-name" "^7.0.0"
"@babel/plugin-transform-literals" "^7.0.0"
"@babel/plugin-transform-modules-commonjs" "^7.0.0"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0"
"@babel/plugin-transform-parameters" "^7.0.0"
"@babel/plugin-transform-react-display-name" "^7.0.0"
"@babel/plugin-transform-react-jsx" "^7.0.0"
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
"@babel/plugin-transform-runtime" "^7.0.0"
"@babel/plugin-transform-shorthand-properties" "^7.0.0"
"@babel/plugin-transform-spread" "^7.0.0"
"@babel/plugin-transform-sticky-regex" "^7.0.0"
"@babel/plugin-transform-typescript" "^7.5.0"
"@babel/plugin-transform-unicode-regex" "^7.0.0"
"@babel/template" "^7.0.0"
babel-plugin-transform-flow-enums "^0.0.2"
react-refresh "^0.4.0"
metro-react-native-babel-transformer@0.76.8: metro-react-native-babel-transformer@0.76.8:
version "0.76.8" version "0.76.8"
resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.8.tgz#c3a98e1f4cd5faf1e21eba8e004b94a90c4db69b" resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.8.tgz#c3a98e1f4cd5faf1e21eba8e004b94a90c4db69b"
@ -8514,11 +8652,6 @@ react-native-svg@^6.2.1:
lodash "^4.16.6" lodash "^4.16.6"
pegjs "^0.10.0" pegjs "^0.10.0"
react-native-vision-camera@^3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/react-native-vision-camera/-/react-native-vision-camera-3.8.2.tgz#f4f75f84c6a19e1c3474ddc0f7f785b5a526739b"
integrity sha512-MY39l2e3hNRPUefn2JPShOFExcw0PblbAcUGvJrIfS9pMzdIyceo0umRAx8lOGXzDUAdb+xy/tFWb8zGxKimCQ==
react-native@*: react-native@*:
version "0.73.2" version "0.73.2"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.73.2.tgz#74ee163c8189660d41d1da6560411da7ce41a608" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.73.2.tgz#74ee163c8189660d41d1da6560411da7ce41a608"
@ -9177,7 +9310,7 @@ slice-ansi@^2.0.0:
astral-regex "^1.0.0" astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0" is-fullwidth-code-point "^2.0.0"
slugify@^1.3.4: slugify@^1.3.4, slugify@^1.6.6:
version "1.6.6" version "1.6.6"
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b" resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b"
integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw== integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==
@ -9434,6 +9567,19 @@ structured-headers@^0.4.1:
resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1" resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1"
integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg== integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==
sucrase@3.34.0:
version "3.34.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f"
integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.2"
commander "^4.0.0"
glob "7.1.6"
lines-and-columns "^1.1.6"
mz "^2.7.0"
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
sucrase@^3.20.0: sucrase@^3.20.0:
version "3.35.0" version "3.35.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"